monacousa-portal/components/ui/AnimatedNumber.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>