Skip to Content
Course content

140: Using requestAnimationFrame Effectively

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running an old-school film projector. The machine has a shutter that opens and closes at a very precise rhythm—say, 24 times a second. If you try to slide a new frame of film into place whenever you feel like it, regardless of where the shutter is, you'll get a jittery, flickering mess. The image will tear, or you'll skip frames entirely. To get that buttery-smooth cinematic motion, you have to time your frame swaps exactly to the beat of the projector's shutter.

In the browser, the "shutter" is the display refresh rate (usually 60Hz, though it varies on modern monitors). When you use setInterval or setTimeout for animations, you're essentially guessing when the shutter is open. You might push an update just after the browser has already repainted the screen, meaning your update sits there doing nothing until the next cycle. That's where "jank" comes from. requestAnimationFrame (or rAF) is your way of telling the browser, "I have an update ready; please execute it exactly when you're about to paint the next frame."

Stopping the Fight with the Browser's Heartbeat

The biggest win with rAF isn't just smoothness—it's efficiency. I've seen countless apps chew through laptop batteries because they were running setInterval loops in background tabs. The browser is smart; if a tab is hidden, it stops calling requestAnimationFrame. Your animation effectively pauses, saving the user's CPU and battery without you having to write a single line of "tab-visibility" logic.

Here is the basic pattern. Notice how the function calls itself recursively, but only when the browser is ready:

const box = document.querySelector('.box');
let position = 0;

function update() {
  position += 2; 
  box.style.transform = `translateX(${position}px)`;

  // We ask the browser to call this again before the next repaint
  requestAnimationFrame(update);
}

// Start the loop
requestAnimationFrame(update);

The Trap of the Fixed Increment

Look closely at the code above. I'm adding 2px every frame. On a standard 60Hz monitor, that box moves at a predictable speed. But what happens if your user has a 144Hz gaming monitor? The update function will run more than twice as often, and that box will suddenly scream across the screen at double speed. I've seen this bug ruin "smooth" UI transitions on high-end hardware.

To fix this, we use the timestamp that rAF automatically passes to your callback. This allows us to calculate movement based on actual time elapsed rather than frames passed. This is called "frame-rate independence."

let start = null;
const distance = 500; // Move 500px total
const duration = 2000; // Over 2 seconds

function animate(timestamp) {
  if (!start) start = timestamp;
  
  // Calculate how much time has passed since we started
  const elapsed = timestamp - start;
  
  // Calculate progress as a decimal between 0 and 1
  const progress = Math.min(elapsed / duration, 1);
  
  const currentPos = progress * distance;
  box.style.transform = `translateX(${currentPos}px)`;

  if (progress < 1) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

When to Actually Use This

I'll be honest: you don't always need rAF. If you're just moving a sidebar in and out, a CSS transition or animation is almost always better because the browser handles those on a separate compositor thread. Use requestAnimationFrame when you're doing something a CSS transition can't handle—like complex physics, canvas drawing, or an element whose destination changes dynamically based on user input (like a custom cursor or a drag-and-drop interface).




📋 Practical Task

Exercise: Building a Frame-Rate Independent Bouncing Orb

Your goal is to create a smooth animation of a circle bouncing horizontally across the screen. However, the orb must move at the exact same speed regardless of whether the user's monitor is 60Hz, 120Hz, or 144Hz.

  • Create a div with the class .orb (style it as a circle with a fixed width/height).
  • Use requestAnimationFrame to animate the orb from the left side of the screen to the right and back again.
  • Requirement: Instead of adding a fixed number of pixels per frame, calculate the position using the timestamp argument. The orb should take exactly 3 seconds to travel from one side to the other.
  • Requirement: Ensure the animation loops indefinitely.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.