requestAnimationFrame — Smooth Animations
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7). Detection hints are not specified for this term. Common mistakes like not using the timestamp argument, forgetting to cancel animation IDs, or calling rAF inside resize handlers are not caught by default linters or compiler checks — they require runtime profiling, frame inspection tools, or manual code review to detect jank and memory leaks.
Closest to 'simple parameterised fix' (e3). The quick_fix shows a straightforward replacement pattern: swap setInterval(draw, 16) for a rAF loop and use the timestamp argument. Most common mistakes (not using timestamp, missing cancellation, multiple loops) are fixed by refactoring the animation callback within one component, not requiring cross-file changes.
Closest to 'localised tax' (b3). requestAnimationFrame affects only animation-heavy components (canvas, sprite systems, DOM transitions); the rest of the codebase is unaffected. However, any component that uses rAF must respect the contract (use timestamp, store/cancel ID, avoid heavy compute) or face jank and leaks, creating a localised productivity tax on animation features.
Closest to 'serious trap' (t7). The core misconception is that rAF guarantees 60fps — it actually fires at the monitor's refresh rate (30Hz to 144Hz+) and may throttle to 1fps in background tabs. This contradicts common assumptions and differs from how setTimeout behaves (fixed interval). Developers unfamiliar with rAF will hardcode 'pixels per frame' instead of using elapsed time, producing speed variations across devices. The timestamp argument is essential but not intuitive.
Also Known As
TL;DR
Explanation
Before requestAnimationFrame, animations used setTimeout or setInterval with fixed millisecond delays. These ignore the monitor's refresh rate, fire at inconsistent intervals, and continue running in background tabs wasting CPU. requestAnimationFrame synchronises with the browser's render cycle — typically 60fps on most displays, 120fps on high-refresh screens. The callback receives a DOMHighResTimeStamp argument giving the current time, enabling frame-rate-independent animation. When the tab is not visible, the browser throttles or stops rAF calls entirely. cancelAnimationFrame() cancels a pending callback. For complex animations, the Web Animations API or CSS transitions are often preferable — they can run on the compositor thread without blocking the main thread.
Common Misconception
Why It Matters
Common Mistakes
- Not using the timestamp argument — hardcoding speed as 'pixels per frame' makes animation fast on 144Hz and slow on 30Hz; always use elapsed time.
- Forgetting to store and cancel the animation ID — rAF loops that are never cancelled continue running even after components are removed, causing memory leaks.
- Calling rAF from inside a resize event handler — creates multiple parallel animation loops; keep one loop and handle resize state separately.
- Doing heavy computation inside the rAF callback — long tasks block the main thread and skip frames; offload to Web Workers and only do drawing inside rAF.
Code Examples
// ❌ setInterval animation — ignores refresh rate, runs in background
let x = 0;
setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x++, 50, 20, 20);
// Runs every 16ms regardless of monitor refresh rate
// Continues running in background tab — wastes battery
// No sync with display — produces tearing/jank
}, 16);
// ✅ requestAnimationFrame — synced to display, time-based movement
let x = 0;
let lastTime = null;
let animId = null;
function draw(timestamp) {
if (lastTime === null) lastTime = timestamp;
const elapsed = timestamp - lastTime; // ms since last frame
lastTime = timestamp;
const speed = 200; // pixels per second — frame-rate independent
x += speed * (elapsed / 1000);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x % canvas.width, 50, 20, 20);
animId = requestAnimationFrame(draw);
}
// Start
animId = requestAnimationFrame(draw);
// Stop when needed (e.g. component unmount)
function stop() { cancelAnimationFrame(animId); }