feat(stage-ui): introduce poppin'text

This commit is contained in:
Makito
2025-07-08 04:44:57 +09:00
parent db38c2270b
commit b56b8bf74e
6 changed files with 93 additions and 0 deletions
+3
View File
@@ -63,6 +63,7 @@ words:
- demodel
- devlogs
- directml
- dokidoki
- dotenvx
- DownloadLive2DSDK
- dtolnay
@@ -100,6 +101,7 @@ words:
- Kawaii
- kebabcase
- Keyyable
- kirakira
- kwaa
- lemonnekogh
- libsamplerate
@@ -159,6 +161,7 @@ words:
- pixi
- pixiv
- popmotion
- poppin
- PREFLIGHTS
- prereleased
- pretrained
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref } from 'vue'
import PoppinText from './PoppinText.web.vue'
import { popupAnimator } from './animators'
const text = ref('キラキラドキドキ')
</script>
<template>
<Story
title="Poppin'Text"
group="widgets"
:layout="{ type: 'grid', width: '100%' }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="basic-english"
title="English Text"
>
<div h-50 w-50 p-4>
<PoppinText :text="text" :animator="popupAnimator" />
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,39 @@
<!-- Poppin'Text - Makes your text "kirakira dokidoki"!! -->
<script setup lang="ts">
import type { Animator } from './animators'
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
const props = defineProps<{
text: string
animator?: Animator
}>()
const graphemeClusters = computed(() => [...props.text]) // Safely split text into grapheme clusters with UTF-8 awareness
const elements = ref<HTMLElement[]>([])
const animatorCleanupFn = shallowRef<() => void>()
onMounted(() => {
animatorCleanupFn.value = props.animator?.(elements.value)
})
watch([graphemeClusters, () => props.animator], ([_, animator]) => {
animatorCleanupFn.value?.()
animatorCleanupFn.value = animator?.(elements.value)
}, { flush: 'post' }) // <- Ensure post-update refs
</script>
<template>
<div class="relative overflow-hidden">
<span
v-for="(cluster, index) in graphemeClusters"
:key="index"
ref="elements"
class="inline-block"
>
{{ cluster }}
</span>
</div>
</template>
@@ -0,0 +1,3 @@
export type Animator = (elements: HTMLElement[]) => (() => void)
export * from './popup'
@@ -0,0 +1,17 @@
import type { Animator } from '.'
import { createTimeline } from 'animejs'
export const popupAnimator: Animator = (elements: HTMLElement[]) => {
const timeline = createTimeline({ loop: true })
.add(elements, {
translateY: ['1.1em', 0],
translateZ: 0,
duration: 750,
delay: (el, i) => 50 * i,
})
return () => {
timeline.remove(elements)
}
}
@@ -1,3 +1,4 @@
export * from './Dialogs'
export { default as PoppingSubtitles } from './PoppingSubtitles.web.vue'
export { default as PoppinText } from './PoppinText/PoppinText.web.vue'
export * from './Toasters'