oriyo_next/src/shared/components/animated-counter.tsx

74 lines
1.7 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
interface AnimatedCounterProps {
end: number;
duration?: number;
prefix?: string;
suffix?: string;
decimals?: number;
className?: string;
}
export default function AnimatedCounter({
end,
duration = 2000,
prefix = '',
suffix = '',
decimals = 0,
className = '',
}: AnimatedCounterProps) {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const startTimeRef = useRef<number | null>(null);
const frameRef = useRef<number | null>(null);
useEffect(() => {
// Reset when end value changes
countRef.current = 0;
startTimeRef.current = null;
setCount(0);
const animate = (timestamp: number) => {
if (startTimeRef.current === null) {
startTimeRef.current = timestamp;
}
const progress = timestamp - startTimeRef.current;
const percentage = Math.min(progress / duration, 1);
// Easing function for smoother animation
const easeOutQuart = 1 - Math.pow(1 - percentage, 4);
const currentCount = Math.min(easeOutQuart * end, end);
countRef.current = currentCount;
setCount(currentCount);
if (percentage < 1) {
frameRef.current = requestAnimationFrame(animate);
}
};
frameRef.current = requestAnimationFrame(animate);
return () => {
if (frameRef.current !== null) {
cancelAnimationFrame(frameRef.current);
}
};
}, [end, duration]);
const formatNumber = (num: number) => {
return num.toFixed(decimals);
};
return (
<span className={className}>
{prefix}
{formatNumber(count)}
{suffix}
</span>
);
}