37 lines
1.0 KiB
JavaScript
37 lines
1.0 KiB
JavaScript
import { useEffect, useRef, useState } from 'react'
|
|
|
|
export default function CountUp({ end, duration = 1000, suffix = '' }) {
|
|
const [display, setDisplay] = useState(0)
|
|
const ref = useRef(null)
|
|
const started = useRef(false)
|
|
|
|
useEffect(() => {
|
|
const el = ref.current
|
|
if (!el) return
|
|
|
|
const observer = new IntersectionObserver(([entry]) => {
|
|
if (entry.isIntersecting && !started.current) {
|
|
started.current = true
|
|
const startTime = performance.now()
|
|
|
|
const animate = (now) => {
|
|
const elapsed = now - startTime
|
|
const progress = Math.min(elapsed / duration, 1)
|
|
// ease-out quad
|
|
const eased = 1 - (1 - progress) * (1 - progress)
|
|
setDisplay(Math.round(eased * end))
|
|
if (progress < 1) requestAnimationFrame(animate)
|
|
}
|
|
|
|
requestAnimationFrame(animate)
|
|
observer.disconnect()
|
|
}
|
|
}, { threshold: 0.3 })
|
|
|
|
observer.observe(el)
|
|
return () => observer.disconnect()
|
|
}, [end, duration])
|
|
|
|
return <span ref={ref}>{display}{suffix}</span>
|
|
}
|