軌道單選器 radio
可以抓住球拖曳並拋出,球會受到各個選項黑洞的引力影響,最終被最近的黑洞捕獲。
也可以直接點選選項,球會自動飛過去。
技術關鍵字
| 名稱 | 描述 |
|---|---|
| Pointer 事件 | 偵測滑鼠或觸控點移動、點擊、懸停等等事件,取得座標、目標等等資訊 |
| 物理模擬 | 模擬真實世界物理現象,如重力、碰撞、速度等物理效果 |
| JS 動畫 | 基於 JavaScript 實現的動畫,達成更複雜、精準的動畫控制,常見套件有 GSAP、anime.js 等 |
使用範例
基本用法
不知道喝甚麼?放手交給物理學吧 (ノ>∀<)ノ ⌒*
珍珠奶茶
紅茶
可可
柳橙汁
查看範例原始碼
vue
<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>原理
每個選項就像宇宙中的黑洞,對球施加引力。
球被拋出後,根據牛頓萬有引力公式計算加速度,同時透過阻尼係數模擬空氣阻力讓球逐漸減速。
路人:「太空甚麼時候有空氣阻力了?」
鱈魚:「...我說有就是有 ( ・ิω・ิ)」
當球靠近某個黑洞且速度低於閾值時,就會被捕獲,觸發選取事件。
具體流程如下:
- 使用 Pointer Events 追蹤拖曳軌跡,透過滑動時間窗口計算釋放瞬間的速度。
- 釋放後進入物理模擬,每幀計算所有黑洞對球的引力加速度。
- 加入阻尼讓球逐漸減速,碰到視窗邊界則反彈。
- 球進入捕捉範圍且速度夠低時,吸入黑洞完成選取。
重力軟化(Softening)
在真實的 N 體模擬中,當兩個物體距離趨近於零時,引力公式中的分母也趨近於零,導致加速度趨近無限大。
重力軟化參數就是在分母加上一個小值,避免數值爆炸。∠( ᐛ 」∠)_
原始碼
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];
}