useFrame is a hook that calls your callback on every animation frame using requestAnimationFrame. The callback receives useful timing information:
frame— the frame number, starting at1;deltaTime— milliseconds elapsed since the previous frame;timeSinceStart— milliseconds elapsed since the first frame.
This hook is useful for animations, syncing with the screen refresh rate, measurements, and incremental computations.
function useFrame(callback: UseFrameCallback): void;-
Parameters
callback— function called on eachrequestAnimationFramewith timing info.
-
Returns
void— no direct value; the hook only subscribes/unsubscribes to frames.
import { useFrame } from '@webeach/react-hooks/useFrame';
export function Logger() {
useFrame(({ frame, deltaTime, timeSinceStart }) => {
if (frame % 60 === 0) {
console.log('frame', frame, 'Δ', deltaTime.toFixed(2), 'ms', 'total', timeSinceStart.toFixed(2), 'ms');
}
});
return null;
}import { useRef } from 'react';
import { useFrame } from '@webeach/react-hooks/useFrame';
export function BoxMover() {
const boxRef = useRef<HTMLDivElement>(null);
const xRef = useRef(0);
useFrame(({ deltaTime }) => {
xRef.current += 0.1 * deltaTime; // 0.1px per millisecond (≈6px/frame at 60fps)
if (boxRef.current) {
boxRef.current.style.transform = `translateX(${xRef.current}px)`;
}
});
return <div ref={boxRef} style={{ width: 40, height: 40, backgroundColor: 'green' }} />;
}import { useState } from 'react';
import { useFrame } from '@webeach/react-hooks/useFrame';
import { useToggle } from '@webeach/react-hooks/useToggle';
export function Clock() {
const [paused, togglePaused] = useToggle(false);
const [ms, setMs] = useState(0);
useFrame(({ deltaTime }) => {
if (paused) return;
setMs((prev) => prev + deltaTime);
});
return (
<div>
<output>{ms.toFixed(0)} ms</output>
<button onClick={togglePaused}>{paused ? 'Resume' : 'Pause'}</button>
</div>
);
}-
Frame frequency
- The callback is executed once per animation frame; frequency depends on device and tab activity.
-
Timings
- The callback receives
{ frame, deltaTime, timeSinceStart }, which allows building frame‑independent animations.
- The callback receives
-
Fresh logic
- The hook always calls the latest version of
callback. Updates to props/state are reflected automatically without re-subscribing.
- The hook always calls the latest version of
-
Cleanup
- On unmount, the animation frame is canceled to prevent leaks.
-
Layout phase
- The subscription is registered in the layout effect phase, ensuring no frames are missed before paint.
- Frame-based animations and interpolations (position, opacity, counters).
- Smooth integrations with Canvas/WebGL/SVG rendering.
- Regular measurements or syncing logic with frame timing.
- For rare or one-off actions —
setTimeout/useEffectmay be enough. - For heavy computations every frame — distribute work or move it to a Worker to avoid blocking the UI.
-
Heavy logic inside the callback
- Long tasks increase
deltaTimeand make animations stutter. Keep frame logic lightweight.
- Long tasks increase
-
Assuming fixed 60fps
- Actual frame rate depends on the device and system load. Always use
deltaTimefor consistent animations.
- Actual frame rate depends on the device and system load. Always use
-
Mutating refs without re-render
- Updating a plain variable won’t re-render the component. Use
useStateif UI needs to react.
- Updating a plain variable won’t re-render the component. Use
Exported types
UseFrameCallback(info: { frame: number; deltaTime: number; timeSinceStart: number }) => void— function called on each frame with timing info.