Files
gongxue-new/docs/.vitepress/theme/components/CountUpNumber.vue

69 lines
1.7 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
interface Props {
end: number
duration?: number
suffix?: string
decimals?: number
}
const props = withDefaults(defineProps<Props>(), {
duration: 2000,
suffix: '',
decimals: 0,
})
const count = ref(0)
const refEl = ref<HTMLDivElement | null>(null)
let hasAnimated = false
let rafId: number | null = null
let observer: IntersectionObserver | null = null
onMounted(() => {
observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && !hasAnimated) {
hasAnimated = true
let startTime: number | null = null
const animate = (currentTime: number) => {
if (!startTime) startTime = currentTime
const progress = Math.min((currentTime - startTime) / props.duration, 1)
const easeProgress = 1 - Math.pow(1 - progress, 4)
count.value = props.end * easeProgress
if (progress < 1) {
rafId = requestAnimationFrame(animate)
} else {
count.value = props.end
}
}
rafId = requestAnimationFrame(animate)
}
},
{ threshold: 0.5 }
)
if (refEl.value) {
observer.observe(refEl.value)
}
})
onUnmounted(() => {
if (observer && refEl.value) observer.disconnect()
if (rafId) cancelAnimationFrame(rafId)
})
</script>
<template>
<div
ref="refEl"
class="text-4xl md:text-5xl font-bold text-apple-dark mb-2 transition-transform group-hover:scale-110 duration-300 tabular-nums"
>
{{ count.toFixed(props.decimals) }}
<span v-if="props.suffix" class="text-2xl ml-1 font-medium text-gray-400">{{ props.suffix }}</span>
</div>
</template>