"use client"; import { useEffect, useRef, useState } from "react"; interface CounterProps { target: number; suffix?: string; prefix?: string; duration?: number; className?: string; title?: string; numberClassName?: string; titleClassName?: string; layout?: "default" | "row-reverse"; gap?: string; style?: React.CSSProperties; } export function Counter({ target, suffix = "", prefix = "", duration = 2000, className = "", title, numberClassName = "", titleClassName = "", layout = "default", gap = "0px", style, }: CounterProps) { const [count, setCount] = useState(0); const [hasAnimated, setHasAnimated] = useState(false); const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !hasAnimated) { setHasAnimated(true); const startTime = performance.now(); const animate = (now: number) => { const elapsed = now - startTime; const progress = Math.min(elapsed / duration, 1); // ease-out const eased = 1 - Math.pow(1 - progress, 3); setCount(Math.floor(eased * target)); if (progress < 1) requestAnimationFrame(animate); }; requestAnimationFrame(animate); } }, { threshold: 0.3 } ); observer.observe(el); return () => observer.disconnect(); }, [target, duration, hasAnimated]); return (
{prefix && {prefix}} {count} {suffix && {suffix}}
{title && (
{title}
)}
); }