每條游來游去的小魚,都是正在瀏覽這一頁的訪客,有一條是你自己 ( ´ ▽ ` )ノ 覺得干擾閱讀的話,可以把小魚關掉。
拉炮
util隨時隨地都可以慶祝!✧。٩(ˊᗜˋ*)و✧*。
| 名稱 | 描述 |
|---|---|
| Babylon.js | 3D 引擎 |
| 粒子系統 | 產生大量小物件的系統,常用於模擬煙霧、火焰、雨雪等效果 |
| 物理模擬 | 模擬真實世界物理現象,如重力、碰撞、速度等物理效果 |
| Dynamic Texture | 可動態修改的材質,常用於即時產生圖案或文字 |
呼叫 emit 即可發射粒子。
🎉
🎉
🎉
🎉
🎉
🎇
0
<template> <div class="w-full flex flex-col gap-4 example-wrap"> <util-party-popper ref="popperRef" v-slot="{ fps }" class="h-[60vh] flex" :max-concurrency="40" > <div class="h-full w-full flex flex-col items-center justify-center gap-6"> <div class="flex gap-6"> <div class="cursor-pointer select-none rounded p-4 text-4xl -rotate-90" @click="(event) => emit(event, 'lt')" > 🎉 </div>
<div class="cursor-pointer select-none rounded p-4 text-4xl -rotate-45" @click="(event) => emit(event, 't')" > 🎉 </div>
<div class="cursor-pointer select-none rounded p-4 text-4xl" @click="(event) => emit(event, 'rt')" > 🎉 </div> </div>
<div class="flex gap-10"> <div class="cursor-pointer select-none rounded p-4 text-4xl -rotate-[135deg]" @click="(event) => emit(event, 'l')" > 🎉 </div>
<div class="rotate-45 cursor-pointer select-none rounded p-4 text-4xl" @click="(event) => emit(event, 'r')" > 🎉 </div> </div>
<div class="cursor-pointer select-none rounded p-4 text-4xl" @click="emit" > 🎇 </div> </div>
<div class="absolute left-0 top-0 p-4"> {{ fps }} </div> </util-party-popper> </div></template>
<script setup lang="ts">import { Scalar } from '@babylonjs/core'import { useElementBounding } from '@vueuse/core'import { conditional, constant, isDeepEqual } from 'remeda'import { ref } from 'vue'import UtilPartyPopper from '../util-party-popper.vue'
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = useElementBounding(popperRef)
type Direction = 'rt' | 'lt' | 't' | 'l' | 'r'
function emit( payload: MouseEvent, direction?: Direction,) { const target = payload.target if (!(target instanceof Element)) return
const bounding = target.getBoundingClientRect()
const position = { x: bounding.left + bounding.width / 2 - popperBounding.left.value, y: bounding.top + bounding.height / 2 - popperBounding.top.value, }
const velocityRange = { min: 2, max: 8 }
if (!direction) { popperRef.value?.emit(position) return }
const params = conditional( direction, [ isDeepEqual('rt'), constant(() => ({ ...position, velocity: { x: -Scalar.RandomRange(velocityRange.min, velocityRange.max), y: Scalar.RandomRange(velocityRange.min, velocityRange.max), }, })), ], [ isDeepEqual('lt'), constant(() => ({ ...position, velocity: { x: Scalar.RandomRange(velocityRange.min, velocityRange.max), y: Scalar.RandomRange(velocityRange.min, velocityRange.max), }, })), ], [ isDeepEqual('t'), constant(() => ({ ...position, velocity: { x: Scalar.RandomRange(-2, 2), y: Scalar.RandomRange(velocityRange.min, velocityRange.max), }, })), ], [ isDeepEqual('l'), constant(() => ({ ...position, velocity: { x: Scalar.RandomRange(velocityRange.min, velocityRange.max), y: Scalar.RandomRange(-2, 2), }, })), ], [ isDeepEqual('r'), constant(() => ({ ...position, velocity: { x: -Scalar.RandomRange(velocityRange.min, velocityRange.max), y: Scalar.RandomRange(-2, 2), }, })), ], )
popperRef.value?.emit(params)}</script>不只可自訂發射位置,粒子發射範圍也可以調整。
廣域發射更有派對的感覺!✧⁑。٩(ˊᗜˋ*)و✧⁕。
👈
👆
👇
🎆
👉
<template> <div class="w-full flex flex-col gap-4"> <div class="h-full w-full flex items-center justify-center gap-10 p-10"> <div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit('left')" > 👈 </div>
<div class="flex flex-col gap-10"> <div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit('top')" > 👆 </div>
<div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit('bottom')" > 👇 </div>
<div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit('bottom-center')" > 🎆 </div> </div>
<div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit('right')" > 👉 </div> </div>
<util-party-popper ref="popperRef" class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed" :quantity-of-per-emit="100" :max-concurrency="50" /> </div></template>
<script setup lang="ts">import { Scalar } from '@babylonjs/core'import { useElementBounding } from '@vueuse/core'import { conditional, constant, isDeepEqual } from 'remeda'
import { ref } from 'vue'
import UtilPartyPopper from '../util-party-popper.vue'
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = useElementBounding(popperRef)
function emit(position: 'top' | 'bottom' | 'left' | 'right' | 'bottom-center') { const offset = 50
const params = conditional( position, [ isDeepEqual('top'), constant(() => ({ x: Scalar.RandomRange(0, popperBounding.width.value), y: -offset, velocity: { x: Scalar.RandomRange(1, -1), y: Scalar.RandomRange(0, -10), }, })), ], [ isDeepEqual('bottom'), constant(() => ({ x: Scalar.RandomRange(0, popperBounding.width.value), y: popperBounding.height.value + offset, velocity: { x: Scalar.RandomRange(1, -1), y: Scalar.RandomRange(10, 15), }, })), ], [ isDeepEqual('bottom-center'), () => ({ x: Scalar.RandomRange(0, popperBounding.width.value), y: popperBounding.height.value + offset, velocity: { x: 0, y: Scalar.RandomRange(8, 20), }, }), ], [ isDeepEqual('left'), constant(() => ({ x: -offset, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: Scalar.RandomRange(-5, -10), y: Scalar.RandomRange(-1, 1), }, })), ], [ isDeepEqual('right'), constant(() => ({ x: popperBounding.width.value + offset, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-1, 1), }, })), ], )
popperRef.value?.emit(params)}</script>不只是方形,還有各種形狀可以選擇。
🎉
<template> <div class="w-full flex flex-col gap-4"> <div class="h-full w-full flex items-center justify-center gap-10 p-10"> <div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit()" > 🎉 </div> </div>
<util-party-popper ref="popperRef" :confetti="confettiList" class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed" :quantity-of-per-emit="50" :max-concurrency="50" /> </div></template>
<script setup lang="ts">import type { ExtractArrayType } from '../../../types/main.type'import { Scalar } from '@babylonjs/core'import { useElementBounding } from '@vueuse/core'import { ref } from 'vue'import UtilPartyPopper from '../util-party-popper.vue'
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = useElementBounding(popperRef)
type Confetti = ExtractArrayType< InstanceType<typeof UtilPartyPopper>['confetti']>const confettiList: Confetti[] = [ { shape: 'plane', width: 10, height: 10, }, { shape: 'cylinder', diameter: 10, height: 1, }, { shape: 'disc', radius: 10, tessellation: 3, arc: 1, }, { shape: 'disc', radius: 8, tessellation: 8, arc: 1, }, { shape: 'torus', diameter: 12, thickness: 2, },]
function emit() { popperRef.value?.emit(() => ({ x: 0, y: popperBounding.height.value, velocity: { x: -Scalar.RandomRange(5, 10), y: Scalar.RandomRange(10, 20), }, }))
popperRef.value?.emit(() => ({ x: popperBounding.width.value, y: popperBounding.height.value, velocity: { x: Scalar.RandomRange(5, 10), y: Scalar.RandomRange(10, 20), }, }))}</script>不只形狀,還可以使用文字,有更多理由可以慶祝了。
例如鱈魚又胖了 2 公斤!(/≧▽≦)/
🎉
鱈魚:「這種事別拿出來慶祝啊!╭(°A ,°`)╮」
查看範例原始碼
<!-- #region main-code --><template> <div class="w-full flex flex-col gap-4"> <div class="h-full w-full flex items-center justify-center gap-10 p-10"> <div class="cursor-pointer select-none rounded px-4 py-2 text-2xl" @click="emit()" > 🎉 </div> </div>
<util-party-popper ref="popperRef" :confetti="confettiList" class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed" :quantity-of-per-emit="20" :max-concurrency="50" :max-angular-velocity="Math.PI / 100" :color="{ r: 1, g: 1, b: 1 }" /> </div></template>
<script setup lang="ts">import type { ExtractArrayType } from '../../../types/main.type'import { Scalar } from '@babylonjs/core'import { useElementBounding } from '@vueuse/core'import { ref } from 'vue'import { useI18n } from 'vue-i18n'import UtilPartyPopper from '../util-party-popper.vue'
const { t } = useI18n()
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = useElementBounding(popperRef)
type Confetti = ExtractArrayType< InstanceType<typeof UtilPartyPopper>['confetti']>const confettiList: Confetti[] = [ { shape: 'text', width: 40, height: 40, char: '🐟', }, { shape: 'text', width: 40, height: 20, char: t('肥魚'), }, { shape: 'text', width: 80, height: 40, char: '2 KG!', }, { shape: 'text', width: 30, height: 30, char: '✨', },]
function emit() { popperRef.value?.emit(() => ({ x: 0, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: -Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-5, 5), }, }))
popperRef.value?.emit(() => ({ x: popperBounding.width.value, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-5, 5), }, }))}</script><!-- #endregion main-code -->
<i18n lang="json">{ "zh-hant": { "肥魚": "肥魚" }, "en": { "肥魚": "Fat Fish" }}</i18n>想撒甚麼就撒甚麼!੭ ˙ᗜ˙ )੭
🎉
<!-- #region main-code --><template> <div class="w-full flex flex-col gap-4"> <div class="h-full w-full flex items-center justify-center gap-10 p-6"> <base-input v-model="text" :label="t('輸入 emoji')" />
<div class="cursor-pointer select-none rounded px-4 py-2 text-4xl" @click="emit()" > 🎉 </div> </div>
<util-party-popper ref="popperRef" :key :confetti="confettiList" class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed" :quantity-of-per-emit="50" :max-concurrency="50" :max-angular-velocity="Math.PI / 100" :color="{ r: 1, g: 1, b: 1 }" /> </div></template>
<script setup lang="ts">import type { ExtractArrayType } from '../../../types/main.type'import { Scalar } from '@babylonjs/core'import { useElementBounding, watchDebounced } from '@vueuse/core'import { nanoid } from 'nanoid'import { map, pipe } from 'remeda'import { computed, ref } from 'vue'import { useI18n } from 'vue-i18n'import BaseInput from '../../base-input.vue'import UtilPartyPopper from '../util-party-popper.vue'
const { t } = useI18n()
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = useElementBounding(popperRef)
const text = ref('🎈✨🎉🍖🐟🎁💎')/** 用於強制更新元件 */const key = ref(nanoid())watchDebounced(text, () => { key.value = nanoid()}, { debounce: 1000 })
type Confetti = ExtractArrayType< InstanceType<typeof UtilPartyPopper>['confetti']>const confettiList = computed(() => pipe( text.value.split(/.*?/u), map((char) => ({ shape: 'text', width: 30, height: 30, char, } satisfies Confetti)),))
function emit() { popperRef.value?.emit(() => ({ x: 0, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: -Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-5, 5), }, }))
popperRef.value?.emit(() => ({ x: popperBounding.width.value, y: Scalar.RandomRange(0, popperBounding.height.value), velocity: { x: Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-5, 5), }, }))}</script><!-- #endregion main-code -->
<i18n lang="json">{ "zh-hant": { "輸入 emoji": "輸入 emoji" }, "en": { "輸入 emoji": "Enter emoji" }}</i18n>沿著滑鼠軌跡一路發射彩帶吧。
勞贖!滿滿的勞贖!Σ(ˊДˋ;)
<!-- #region main-code --><template> <div class="w-full flex flex-col gap-4"> <base-checkbox v-model="enable" class="example-ctrl" :label="t('啟用')" />
<util-party-popper ref="popperRef" class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed" :confetti="confettiList" :quantity-of-per-emit="2" :max-concurrency="500" :max-angular-velocity="Math.PI / 100" :color="{ r: 1, g: 1, b: 1 }" /> </div></template>
<script setup lang="ts">import type { ExtractArrayType } from '../../../types/main.type'import { Scalar } from '@babylonjs/core'import { throttleFilter, useMouseInElement, useMousePressed, whenever } from '@vueuse/core'import { ref, watch } from 'vue'import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'import UtilPartyPopper from '../util-party-popper.vue'
const { t } = useI18n()const enable = ref(false)
type Confetti = ExtractArrayType< InstanceType<typeof UtilPartyPopper>['confetti']>const confettiList: Confetti[] = [ { shape: 'text', width: 30, height: 30, char: '🐁', }, { shape: 'text', width: 40, height: 40, char: '🐀', },]
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const { elementX, elementY,} = useMouseInElement(popperRef, { eventFilter: throttleFilter(10), scroll: false,})const { pressed } = useMousePressed()
whenever(pressed, () => { if (!enable.value) return
popperRef.value?.emit(() => ({ x: elementX.value, y: elementY.value, velocity: { x: Scalar.RandomRange(-10, 10), y: Scalar.RandomRange(-10, 10), }, }))})
watch(() => [elementX, elementY], () => { if (!enable.value) return
popperRef.value?.emit(() => ({ x: elementX.value, y: elementY.value, velocity: { x: Scalar.RandomRange(-2, 2), y: Scalar.RandomRange(-2, 2), }, }))}, { deep: true })</script><!-- #endregion main-code -->
<i18n lang="json">{ "zh-hant": { "啟用": "啟用" }, "en": { "啟用": "Enable" }}</i18n>提升填寫表單的動力。(´,,•ω•,,)
您覺得酷酷的元件趣味程度可得幾分? *
<!-- #region main-code --><template> <div class="relative w-full flex items-center justify-center gap-10 border rounded-xl p-10"> <div class="flex flex-col gap-4"> <base-input v-model="form.howToKnow" :label="t('從何得知酷酷的元件 *')" @blur="handleBlur('howToKnow')" />
<div> <div class="text-sm font-bold"> {{ t('您覺得酷酷的元件趣味程度可得幾分? *') }} </div>
<div class="flex justify-around gap-2 py-2"> <label v-for="i in 6" :key="i" @mouseup="emitFromClick" > <input v-model.number="form.score" type="radio" :value="i - 1" > {{ i - 1 }} </label> </div> </div>
<base-input v-model="form.text" :label="t('留給鱈魚的話或鼓勵 *')" @blur="handleBlur('text')" />
<base-btn class="mt-6" :label="t('送出')" @click="submit" />
<transition name="opacity"> <div v-if="isSubmitted" class="absolute inset-0 z-[40] flex flex-col items-center justify-center gap-6 rounded-xl bg-[#c7f6ff] bg-opacity-90 text-black" @click="reset" > <span class="text-xl tracking-wide"> {{ t('表單已送出') }}<br>{{ t('感謝您的回饋!(*´∀`)~♥') }} </span>
<span class="cursor-pointer text-xs"> {{ t('(點一下再來一次)') }} </span> </div> </transition>
<util-party-popper ref="popperRef" class="pointer-events-none left-0 top-0 z-50 h-full w-full !absolute" :quantity-of-per-emit :max-concurrency="100" :confetti="confettiList" /> </div> </div></template>
<script setup lang="ts">import type { ExtractArrayType } from '../../../types'import { Scalar } from '@babylonjs/core'import { promiseTimeout, useElementBounding } from '@vueuse/core'import { conditional, constant, isDeepEqual, pipe } from 'remeda'import { computed, nextTick, reactive, ref } from 'vue'import { useI18n } from 'vue-i18n'import BaseBtn from '../../base-btn.vue'import BaseInput from '../../base-input.vue'import UtilPartyPopper from '../util-party-popper.vue'
const { t } = useI18n()
type Confetti = ExtractArrayType< InstanceType<typeof UtilPartyPopper>['confetti']>
const form = ref({ howToKnow: '', score: undefined, text: '',})const isSubmitted = ref(false)
const popperRef = ref<InstanceType<typeof UtilPartyPopper>>()const popperBounding = reactive(useElementBounding(popperRef))
const confettiList: Confetti[] = [ { shape: 'plane', width: 10, height: 10, }, { shape: 'cylinder', diameter: 10, height: 1, }, { shape: 'disc', radius: 10, tessellation: 3, arc: 1, }, { shape: 'disc', radius: 8, tessellation: 8, arc: 1, }, { shape: 'torus', diameter: 12, thickness: 2, },]const quantityOfPerEmit = ref(50)
async function emitFromEdge(position: 'top' | 'bottom' | 'left' | 'right') { quantityOfPerEmit.value = 50
const offset = 50
const params = conditional( position, [ isDeepEqual('top'), constant(() => ({ x: Scalar.RandomRange(0, popperBounding.width), y: -offset, velocity: { x: Scalar.RandomRange(1, -1), y: Scalar.RandomRange(0, -10), }, })), ], [ isDeepEqual('bottom'), constant(() => ({ x: Scalar.RandomRange(0, popperBounding.width), y: popperBounding.height + offset, velocity: { x: Scalar.RandomRange(1, -1), y: Scalar.RandomRange(10, 15), }, })), ], [ isDeepEqual('left'), constant(() => ({ x: -offset, y: Scalar.RandomRange(0, popperBounding.height), velocity: { x: Scalar.RandomRange(-5, -10), y: Scalar.RandomRange(-1, 1), }, })), ], [ isDeepEqual('right'), constant(() => ({ x: popperBounding.width + offset, y: Scalar.RandomRange(0, popperBounding.height), velocity: { x: Scalar.RandomRange(5, 10), y: Scalar.RandomRange(-1, 1), }, })), ], )
await nextTick() popperRef.value?.emit(params)}async function emitLikeFirework() { quantityOfPerEmit.value = 50 const offset = 50
const params = pipe(undefined, () => ({ x: Scalar.RandomRange(0, popperBounding.width), y: popperBounding.height + offset, velocity: { x: 0, y: Scalar.RandomRange(10, 20), }, }))
await nextTick() popperRef.value?.emit(params)}async function emitFromClick(event: MouseEvent) { // FIX: 等待 form.score 更新,未來在想更好的方法 await promiseTimeout(100)
const score = form.value.score ?? 0 quantityOfPerEmit.value = score * 20
const target = event.target if (!(target instanceof Element)) return
const bounding = target.getBoundingClientRect()
const position = { x: bounding.left + bounding.width / 2 - popperBounding.left, y: bounding.top + bounding.height / 2 - popperBounding.top, }
const velocity = score * 2 const params = pipe(undefined, () => () => ({ ...position, velocity: { x: Scalar.RandomRange(-velocity, velocity), y: Scalar.RandomRange(-velocity, velocity), }, }))
await nextTick()
popperRef.value?.emit(params)}
const canSubmit = computed(() => form.value.howToKnow && form.value.score !== undefined && form.value.text,)
const fieldEdgeMap: Record< keyof typeof form.value, Array<'top' | 'bottom' | 'left' | 'right'>> = { howToKnow: ['top'], score: ['left'], text: ['left', 'right'],}function handleBlur(field: keyof typeof form.value) { if (form.value[field]) { fieldEdgeMap[field].forEach(emitFromEdge) }}
function submit() { if (!canSubmit.value) { // eslint-disable-next-line no-alert alert(t('請完成表單 (´,,•ω•,,)')) return }
for (let i = 0; i < 10; i++) { setTimeout(() => { emitLikeFirework() }, i * 100) }
setTimeout(() => { isSubmitted.value = true }, 1000)}function reset() { isSubmitted.value = false
form.value = { howToKnow: '', score: undefined, text: '', }}</script>
<style lang="sass" scoped>.opacity-enter-active, .opacity-leave-active transition-duration: 0.4s.opacity-enter-from, .opacity-leave-to opacity: 0 !important</style><!-- #endregion main-code -->
<i18n lang="json">{ "zh-hant": { "從何得知酷酷的元件 *": "從何得知酷酷的元件 *", "您覺得酷酷的元件趣味程度可得幾分? *": "您覺得酷酷的元件趣味程度可得幾分? *", "留給鱈魚的話或鼓勵 *": "留給鱈魚的話或鼓勵 *", "送出": "送出", "表單已送出": "表單已送出", "感謝您的回饋!(*´∀`)~♥": "感謝您的回饋!(*´∀`)~♥", "(點一下再來一次)": "(點一下再來一次)", "請完成表單 (´,,•ω•,,)": "請完成表單 (´,,•ω•,,)" }, "en": { "從何得知酷酷的元件 *": "How did you learn about Chill Components? *", "您覺得酷酷的元件趣味程度可得幾分? *": "How fun do you find Chill Components? (rate) *", "留給鱈魚的話或鼓勵 *": "Message or encouragement for Codfish *", "送出": "Submit", "表單已送出": "Form submitted", "感謝您的回饋!(*´∀`)~♥": "Thanks for your feedback! (*´∀`)~♥", "(點一下再來一次)": "(Click to try again)", "請完成表單 (´,,•ω•,,)": "Please complete the form (´,,•ω•,,)" }}</i18n>利用 babylon.js 製作粒子效果。
預設使用 WebGPU,性能好棒棒!。✧。٩(ˊᗜˋ*)و✧*。
詳細說明請見此文章。
type Confetti = { shape: 'plane'; width: number; height: number;} | { shape: 'cylinder'; height: number; diameter: number;} | { shape: 'disc'; radius: number; /** the number of disc/polygon sides */ tessellation: number; /** ratio of the circumference between 0 and 1 */ arc: number;} | { shape: 'torus'; diameter: number; /** number of segments along the circle */ thickness: number;} | { shape: 'polyhedron'; /** polyhedron type in the range。0-14 * * https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra/polyhedra_by_numbers */ type: number; size: number; sizeX?: number; sizeY?: number; sizeZ?: number;} | { shape: 'text'; width: number; height: number; char: string;}
interface Props { /** 紙屑參數。初始化後即固定,不支援動態變更 */ confetti?: Confetti | Confetti[];
/** 每次發射數量 * * @default 20 */ quantityOfPerEmit?: number;
/** 最大同時觸發次數。 * * @default 10 */ maxConcurrency?: number;
/** 最大角速度 * * @default 1.5 */ maxAngularVelocity?: number;
/** 重力 * * @default -0.01 */ gravity?: number;
/** 空氣阻力。速度衰減比率 * * @default 0.985 */ airResistance?: number;
/** 預設發射速度 */ velocity?: Vector | ((index: number) => Vector);
/** 粒子顏色 */ color?: Color | ((index: number) => Color);}Methods
Section titled “Methods”interface Expose { /** 發射粒子,如果提供 function,則可以分別設定粒子參數 */ emit: (param: EmitParam | ((index: number) => EmitParam)) => void; /** 目前畫面 FPS */ fps: Ref<number>;}defineSlots<{ default?: (data: { fps: number }) => unknown;}>()