Files
guanwang/app/components/AnimatedMetric.vue

33 lines
1.1 KiB
Vue

<script setup lang="ts">
const props = defineProps<{ value: string }>()
const root = ref<HTMLElement>()
const shown = ref(props.value)
let observer: IntersectionObserver | undefined
onMounted(() => {
const match = props.value.match(/^(\d+)(.*)$/)
if (!match || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
const target = Number(match[1])
const suffix = match[2]
shown.value = `0${suffix}`
observer = new IntersectionObserver(([entry]) => {
if (!entry?.isIntersecting) return
observer?.disconnect()
const startedAt = performance.now()
const duration = 620
const tick = (now: number) => {
const progress = Math.min(1, (now - startedAt) / duration)
const eased = 1 - Math.pow(1 - progress, 3)
shown.value = `${Math.round(target * eased)}${suffix}`
if (progress < 1) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
}, { threshold: 0.7 })
if (root.value) observer.observe(root.value)
})
onBeforeUnmount(() => observer?.disconnect())
</script>
<template><strong ref="root">{{ shown }}</strong></template>