setInterval(fn, 16)✕ not synced to paint
0
FPS
—
// fires on a timer, not before paint
setInterval(() => {
x += 2;
draw();
}, 16);
requestAnimationFrame✓ synced to display
0
FPS
—
// runs right before each repaint
function loop(t) {
x += 2;
draw();
requestAnimationFrame(loop);
}
Add load and watch setInterval jank while rAF adapts.
Same code, different screens. Both balls move on every frame. The red ball adds a fixed 5px per frame; the green ball uses delta time. Drag the refresh rate up and watch the red ball speed up while green stays locked.
❌ fixed 5px / frame0 px/s
OVERSHOOT →
✅ delta time (300px/sec)0 px/s
// ❌ fixed per frame — speed depends on Hz
x += 5; // 60Hz→300px/s 144Hz→720px/s
// ✅ delta time — same speed everywhere
const dt = (t - last) / 1000; // seconds since last frame
x += 300 * dt; // 300px/sec on ANY display
❌ No cleanupLeaks loops
0
loops still running after unmount
// ❌ loop outlives the component
function loop() {
update();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// unmount → loop keeps running!
✅ cancelAnimationFrameClean
0
loops still running after unmount
// ✅ store id, cancel on cleanup
let id;
function loop() {
update();
id = requestAnimationFrame(loop);
}
id = requestAnimationFrame(loop);
// cleanup:
cancelAnimationFrame(id);