64 lines
1.4 KiB
Vue
64 lines
1.4 KiB
Vue
<template>
|
|
<span>{{ displayValue }}</span>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, onMounted } from 'vue'
|
|
|
|
interface Props {
|
|
value: number
|
|
duration?: number
|
|
format?: (value: number) => string
|
|
delay?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
duration: 1000,
|
|
format: (value: number) => value.toLocaleString(),
|
|
delay: 0
|
|
})
|
|
|
|
const displayValue = ref(props.format(0))
|
|
const startTimestamp = ref<number | null>(null)
|
|
const startValue = ref(0)
|
|
|
|
const animate = (timestamp: number) => {
|
|
if (!startTimestamp.value) {
|
|
startTimestamp.value = timestamp
|
|
}
|
|
|
|
const progress = Math.min((timestamp - startTimestamp.value) / props.duration, 1)
|
|
|
|
// Easing function for smooth animation
|
|
const easeOutQuart = (t: number) => 1 - Math.pow(1 - t, 4)
|
|
const easedProgress = easeOutQuart(progress)
|
|
|
|
const currentValue = startValue.value + (props.value - startValue.value) * easedProgress
|
|
displayValue.value = props.format(currentValue)
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(animate)
|
|
}
|
|
}
|
|
|
|
const startAnimation = () => {
|
|
startTimestamp.value = null
|
|
|
|
if (props.delay > 0) {
|
|
setTimeout(() => {
|
|
requestAnimationFrame(animate)
|
|
}, props.delay)
|
|
} else {
|
|
requestAnimationFrame(animate)
|
|
}
|
|
}
|
|
|
|
watch(() => props.value, (newValue, oldValue) => {
|
|
startValue.value = oldValue || 0
|
|
startAnimation()
|
|
})
|
|
|
|
onMounted(() => {
|
|
startAnimation()
|
|
})
|
|
</script> |