43 lines
1.1 KiB
TypeScript
43 lines
1.1 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
|
|
export function useCountUp(target: number, duration: number = 1000): number {
|
|
const [current, setCurrent] = useState(0);
|
|
const rafRef = useRef<number>(0);
|
|
const currentRef = useRef<number>(0);
|
|
|
|
useEffect(() => {
|
|
if (target === 0) {
|
|
setCurrent(0);
|
|
currentRef.current = 0;
|
|
return;
|
|
}
|
|
|
|
const startTime = performance.now();
|
|
const startValue = currentRef.current;
|
|
|
|
const animate = (now: number) => {
|
|
const elapsed = now - startTime;
|
|
const t = Math.min(elapsed / duration, 1);
|
|
// ease-out cubic: 1 - (1-t)^3
|
|
const eased = 1 - Math.pow(1 - t, 3);
|
|
const value = Math.round(startValue + (target - startValue) * eased);
|
|
setCurrent(value);
|
|
currentRef.current = value;
|
|
|
|
if (t < 1) {
|
|
rafRef.current = requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
|
|
rafRef.current = requestAnimationFrame(animate);
|
|
|
|
return () => {
|
|
if (rafRef.current) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
}
|
|
};
|
|
}, [target, duration]);
|
|
|
|
return current;
|
|
}
|