first commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
// 숫자 카운트업 — 요소가 보이면 0부터 목표값까지 증가 애니메이션
|
||||
// SSR/no-JS 환경에서는 최종값을 그대로 렌더(SEO·접근성), 클라이언트에서만 애니메이션
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
interface Props {
|
||||
value: number
|
||||
duration?: number // ms
|
||||
locale?: boolean // 천 단위 구분기호(ko-KR)
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
duration: 1600,
|
||||
locale: true,
|
||||
})
|
||||
|
||||
const display = ref<number>(props.value) // 초기값 = 최종값 (SSR 노출)
|
||||
const elRef = ref<HTMLElement | null>(null)
|
||||
let io: IntersectionObserver | null = null
|
||||
let rafId = 0
|
||||
|
||||
const formatted = computed(() =>
|
||||
props.locale ? display.value.toLocaleString('ko-KR') : String(display.value),
|
||||
)
|
||||
|
||||
function animate() {
|
||||
const start = performance.now()
|
||||
const from = 0
|
||||
const to = props.value
|
||||
const tick = (now: number) => {
|
||||
const t = Math.min(1, (now - start) / props.duration)
|
||||
// easeOutCubic — 빠르게 시작해 부드럽게 감속
|
||||
const eased = 1 - Math.pow(1 - t, 3)
|
||||
display.value = Math.round(from + (to - from) * eased)
|
||||
if (t < 1) rafId = requestAnimationFrame(tick)
|
||||
}
|
||||
rafId = requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
if (reduceMotion || !elRef.value) return
|
||||
|
||||
display.value = 0 // 클라이언트에서 0부터 시작
|
||||
io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
animate()
|
||||
io?.disconnect()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ threshold: 0.4 },
|
||||
)
|
||||
io.observe(elRef.value)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
io?.disconnect()
|
||||
if (rafId) cancelAnimationFrame(rafId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="elRef">{{ formatted }}</span>
|
||||
</template>
|
||||
Reference in New Issue
Block a user