ninja-code-guard / dashboard /components /AnimatedCounter.tsx
NinjainPJs's picture
initial - commit
4b445f6
"use client";
import { useEffect, useRef, useState } from "react";
interface AnimatedCounterProps {
value: number;
suffix?: string;
duration?: number;
className?: string;
}
export function AnimatedCounter({
value,
suffix = "",
duration = 1200,
className,
}: AnimatedCounterProps) {
const [display, setDisplay] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
const hasAnimated = useRef(false);
useEffect(() => {
if (hasAnimated.current) return;
hasAnimated.current = true;
const start = performance.now();
function tick(now: number) {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
// ease-out expo
const ease = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress);
setDisplay(Math.round(ease * value));
if (progress < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}, [value, duration]);
return (
<span ref={ref} className={className}>
{display}
{suffix}
</span>
);
}