patternjavascriptModerate
requestAnimationFrame for smooth animations instead of setTimeout
Viewed 0 times
requestAnimationFramerAFanimation60fpsvsyncjankframe rate
Problem
Using setTimeout or setInterval for animations causes jank because callbacks fire at arbitrary times, not synchronized to the browser's repaint cycle. This produces dropped frames and visual stutter.
Solution
Use requestAnimationFrame (rAF) for any DOM mutation that is part of an animation. rAF callbacks run just before the browser paints the next frame.
function animate(timestamp) {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
element.style.transform =
if (progress < 1) {
requestAnimationFrame(animate);
}
}
const startTime = performance.now();
requestAnimationFrame(animate);
// Cancel when component unmounts
let rafId;
function start() { rafId = requestAnimationFrame(animate); }
function stop() { cancelAnimationFrame(rafId); }
function animate(timestamp) {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
element.style.transform =
translateX(${progress * 300}px);if (progress < 1) {
requestAnimationFrame(animate);
}
}
const startTime = performance.now();
requestAnimationFrame(animate);
// Cancel when component unmounts
let rafId;
function start() { rafId = requestAnimationFrame(animate); }
function stop() { cancelAnimationFrame(rafId); }
Why
rAF callbacks are batched and executed at the display refresh rate (typically 60Hz or 120Hz). The browser can skip a rAF callback if the tab is hidden, saving CPU. setTimeout is not synchronized to vsync and can fire mid-frame.
Gotchas
- Never do layout reads inside a rAF callback without careful sequencing — reads then writes to avoid layout thrashing
- rAF fires at ~16ms intervals at 60Hz; plan animations using the timestamp parameter, not a frame counter
- CSS transitions and animations are preferable to JavaScript rAF for simple property changes — the browser can optimize them on the compositor thread
- rAF is throttled to ~1fps in background tabs to save battery
Code Snippets
Read-then-write pattern inside rAF to avoid layout thrashing
// Read all layout properties first, then write
function updatePositions(elements) {
requestAnimationFrame(() => {
// Read phase
const boxes = elements.map(el => el.getBoundingClientRect());
// Write phase
elements.forEach((el, i) => {
el.style.transform = `translateY(${boxes[i].top}px)`;
});
});
}Context
When implementing JavaScript-driven animations or reading/writing DOM properties at frame rate
Revisions (0)
No revisions yet.