a hook wrapping a simple requestAnimationFrame based animation function (with an option to dynamically update the duration)
Published on 4 March 2024
import { useEffect, useRef } from 'react'; type draw = (fraction: number) => void; type duration = number; const useAnimation = (duration: duration, draw: draw) => { const updateDurationRef = useRef<null | ((newDuration: number) => void)>(null); const currentAnimationIdRef = useRef(0); const elapsedTimeRef = useRef(0); useEffect(() => { const animate = (duration: duration, draw: draw) => { let start: number | null = null; const animation = (timestamp: number) => { if (!start) start = timestamp; elapsedTimeRef.current = timestamp - start; const percentage = Math.min(elapsedTimeRef.current / duration, 1); draw(percentage); if (elapsedTimeRef.current < duration) { currentAnimationIdRef.current = requestAnimationFrame(animation); } else { start = null; currentAnimationIdRef.current = requestAnimationFrame(animation); } }; const updateDuration = (newDuration: number) => { const currentProgress = elapsedTimeRef.current / duration; start = performance.now() - newDuration * currentProgress; duration = newDuration; cancelAnimationFrame(currentAnimationIdRef.current); currentAnimationIdRef.current = requestAnimationFrame(animation); }; currentAnimationIdRef.current = requestAnimationFrame(animation); return updateDuration; }; updateDurationRef.current = animate(duration, draw); return () => { cancelAnimationFrame(currentAnimationIdRef.current); }; }, [duration, draw]); return updateDurationRef.current; }; export { useAnimation };