feat: 初始化慧遇书院官网一期
This commit is contained in:
32
app/components/AnimatedMetric.vue
Normal file
32
app/components/AnimatedMetric.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user