Skip to content

How to Fix requestAnimationFrame Stuttering in JavaScript

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix requestAnimationFrame Stuttering in JavaScript. We cover key concepts, practical examples, and best practices.

The Problem

requestAnimationFrame animations stutter or jank when a single frame takes longer than 16ms (60 FPS), causing dropped frames and uneven animation pacing.

Quick Fix

Step 1: Use delta time for consistent speed

Frame rates vary, so advance animations by elapsed time, not fixed increments:

let position = 0;
function animate() {
    position += 1;
    element.style.transform = `translateX(${position}px)`;
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

This runs at different speeds on different monitors. Use delta time:

let position = 0;
let lastTime = 0;
function animate(timestamp) {
    const delta = lastTime ? (timestamp - lastTime) / 16.67 : 1;
    lastTime = timestamp;
    position += 1 * delta;
    element.style.transform = `translateX(${position}px)`;
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

Step 2: Avoid layout thrashing

Reading layout properties forces synchronous style recalculation:

function animate() {
    elements.forEach(el => {
        const width = el.offsetWidth;
        el.style.width = (width + 1) + 'px';
    });
    requestAnimationFrame(animate);
}

Batch reads before writes:

function animate() {
    const widths = elements.map(el => el.offsetWidth);
    elements.forEach((el, i) => {
        el.style.width = (widths[i] + 1) + 'px';
    });
    requestAnimationFrame(animate);
}

Step 3: Reduce DOM operations

Move heavy work outside the animation loop:

function animate() {
    const result = expensiveCalculation();
    element.style.transform = `translateX(${result}px)`;
    requestAnimationFrame(animate);
}

Cache calculations and use compositor-only properties:

function animate() {
    element.style.transform = `translateX(${position}px)`;
    position += 1;
    requestAnimationFrame(animate);
}

Use transform and opacity only, as these are composited on the GPU.

Step 4: Use will-change hint

Hint the browser about what will change:

.animated-element {
    will-change: transform, opacity;
}

Prevention

  • Use delta-time calculations for frame-rate-independent animation.
  • Batch DOM reads before writes (fast path).
  • Animate only transform and opacity properties.
  • Keep animation work under 16ms per frame.
  • Use will-change sparingly on elements that animate continuously.

Common Mistakes with requestanimationframe

  1. Using foldl instead of foldl' causing stack overflow on large lists
  2. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  3. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable

These mistakes appear frequently in real-world JS code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What is the difference between requestAnimationFrame and setInterval?

requestAnimationFrame pauses when the tab is inactive, synchronizes with the display refresh rate, and provides a timestamp for delta calculations. setInterval runs regardless of visibility and may queue up multiple callbacks. For animations, always use requestAnimationFrame.

How do I detect frame drops?

Track the time between consecutive frames: let last = performance.now(); function check(timestamp) { const delta = timestamp - last; if (delta > 50) console.warn('Frame dropped, delta:', delta); last = timestamp; requestAnimationFrame(check); }. A consistent delta above 16.67ms indicates performance issues.

Can I run requestAnimationFrame at 30 FPS instead of 60?

Yes. Use a counter to skip frames: let frame = 0; function animate(ts) { frame++; if (frame % 2 === 0) { requestAnimationFrame(animate); return; } doWork(); requestAnimationFrame(animate); }. This skips every other frame for 30 FPS on a 60Hz display.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro