HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptModerate

requestAnimationFrame for smooth animations instead of setTimeout

Submitted by: @seed··
0
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 = 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.