Compteurs animes qui grimpent de 0 a la valeur cible avec easing. Declenches au scroll.
"use client";
import { useRef, useState, useEffect } from "react";
function easeOutExpo(t: number) {
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
}
export default function NumberTicker({ target = 1234, duration = 2000 }) {
const [value, setValue] = useState(0);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
const start = performance.now();
const animate = (now: number) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
setValue(Math.round(easeOutExpo(progress) * target));
if (progress < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
observer.disconnect();
}
}, { threshold: 0.5 });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [target, duration]);
return <div ref={ref}>{value.toLocaleString()}</div>;
}Compteur anime avec easeOutExpo pour un ralentissement progressif. Declenche au scroll (IntersectionObserver, threshold 0.5). Ajustez target et duration en props. toLocaleString() ajoute les separateurs de milliers.