Orbital Radio radio
Grab the ball, drag it and fling it — it will be pulled by the gravity of each option and eventually get captured by the nearest black hole.
You can also click an option directly, and the ball will fly over automatically.
Usage Examples
Basic Usage
Can't decide what to drink? Let physics decide for you (ノ>_<)ノ ⌒*
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6 py-10">
<div class="flex justify-center">
<radio-orbital
v-model="selected"
:options="options"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import RadioOrbital from '../radio-orbital.vue'
const { t } = useI18n()
const options = computed(() => [
{ label: t('bubbleTea'), value: 'bubble-tea' },
{ label: t('blackTea'), value: 'black-tea' },
{ label: t('cocoa'), value: 'cocoa' },
{ label: t('orangeJuice'), value: 'orange-juice' },
])
const selected = ref('bubble-tea')
</script>How It Works
Each option acts like a black hole in space, exerting gravitational pull on the ball. After being flung, the ball's acceleration is calculated using Newton's law of universal gravitation, while a damping coefficient simulates air resistance to gradually slow it down.
When the ball gets close enough to a black hole and its speed drops below a threshold, it gets captured, triggering the selection event.
Here's how it works step by step:
- Pointer Events track the drag trajectory, and a sliding time window computes the velocity at the moment of release.
- After release, a physics simulation kicks in — each frame calculates the gravitational acceleration from all black holes.
- Damping gradually slows the ball down; hitting the viewport boundary causes a bounce.
- When the ball enters capture range with low enough speed, it gets sucked into the black hole and the selection is made.
Gravitational Softening
In real N-body simulations, when two objects get extremely close, the denominator in the gravity formula approaches zero, causing acceleration to skyrocket toward infinity.
The softening parameter adds a small value to the denominator to prevent this numerical explosion. (. ❛ ᴗ ❛.)
Source Code
API
Props
interface RadioOption {
label: string;
value: string;
}
interface Props {
/** 目前選取的值 */
modelValue?: string;
/** 選項列表 */
options?: RadioOption[];
/**
* 球的直徑(px)
* @default 12
*/
ballSize?: number;
/**
* 重力強度
* @default 5_000_000
*/
gravity?: number;
/**
* 阻尼係數(0-1)。數值越小球減速越快,會很快停下來;數值越大球滑行距離越長,減速越慢
* @default 0.4
*/
damping?: number;
/**
* 捕捉距離(px)。球進入此範圍且速度夠低時會被吸入。數值越大越容易被捕捉;數值越小需要更精確地靠近黑洞
* @default 14
*/
captureDistance?: number;
/**
* 捕捉速度閾值(px/s)。球速度低於此值才會被捕捉。數值越大球在較快速度時也能被吸入;數值越小球必須幾乎靜止才會被捕捉
* @default 100
*/
captureSpeed?: number;
/**
* 重力軟化參數,防止球靠近黑洞時加速度爆炸。數值越大重力變化越平滑,球不會突然加速;數值越小球靠近黑洞時會急劇加速
* @default 12
*/
softening?: number;
/**
* 點選黑洞時球移動過去的動畫時長(ms)
* @default 350
*/
moveDuration?: number;
/**
* 主色調,用於球顏色與選中時的邊框色。接受任何 CSS 合法顏色值
* @default '#34c6eb'
*/
color?: string;
}Emits
interface Emits {
'update:modelValue': [value: string];
}