Rocky_Mountain_Vending/components/animated-number.tsx

94 lines
2.2 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
interface AnimatedNumberProps {
value: number
duration?: number
className?: string
}
export function AnimatedNumber({
value,
duration = 2000,
className = "",
}: AnimatedNumberProps) {
const [displayValue, setDisplayValue] = useState(0)
const [isVisible, setIsVisible] = useState(false)
const elementRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isVisible) {
setIsVisible(true)
}
})
},
{ threshold: 0.1 }
)
if (elementRef.current) {
observer.observe(elementRef.current)
}
return () => {
if (elementRef.current) {
observer.unobserve(elementRef.current)
}
}
}, [isVisible])
useEffect(() => {
if (!isVisible || value === 0) {
setDisplayValue(0)
return
}
const startTime = Date.now()
const startValue = 0
const endValue = value
const animate = () => {
const now = Date.now()
const elapsed = now - startTime
const progress = Math.min(elapsed / duration, 1)
// Easing function for bounce effect (ease-out-bounce)
const easeOutBounce = (t: number): number => {
if (t < 1 / 2.75) {
return 7.5625 * t * t
} else if (t < 2 / 2.75) {
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75
} else if (t < 2.5 / 2.75) {
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375
} else {
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375
}
}
const easedProgress = easeOutBounce(progress)
const currentValue = Math.floor(
startValue + (endValue - startValue) * easedProgress
)
setDisplayValue(currentValue)
if (progress < 1) {
requestAnimationFrame(animate)
} else {
setDisplayValue(endValue)
}
}
const frameId = requestAnimationFrame(animate)
return () => cancelAnimationFrame(frameId)
}, [isVisible, value, duration])
return (
<div ref={elementRef} className={className}>
{displayValue}
</div>
)
}