Articles

requestIdleCallback vs requestAnimationFrame

requestAnimationFrame schedules work right before the next repaint; requestIdleCallback runs only when the browser is otherwise idle. When to use each one.

Takina Takina · · 4 min read
Abstract illustration representing a browser API

requestAnimationFrame schedules a callback to run right before the browser’s next repaint, making it the right tool for anything visual; requestIdleCallback schedules a callback to run only when the browser has spare time between other work, making it the right tool for low-priority background tasks. Both exist to keep work off the main thread at the wrong moment — they just define “the wrong moment” differently.

requestAnimationFrame: synced to the repaint

Every frame the browser renders, it runs a sequence of steps: process input, run JavaScript, recalculate style, layout, paint. requestAnimationFrame (often shortened to rAF) schedules your callback to run at a specific point in that sequence — right before the browser is about to paint the next frame.

function animate(timestamp) {
  element.style.transform = `translateX(${getOffset(timestamp)}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

This timing matters for two reasons. First, it’s synchronized to the display’s refresh rate (typically 60 times a second, sometimes higher), so animations driven by rAF are as smooth as the hardware allows — smoother than a setTimeout-based animation, which runs on a fixed timer that has no relationship to when the browser actually paints. Second, the browser automatically pauses rAF callbacks in a background or hidden tab, which setTimeout doesn’t do on its own — saving battery and CPU on work the user can’t even see.

Anything that changes what’s on screen frame by frame belongs here: CSS property animations driven from JavaScript, canvas drawing, scroll-linked effects, game loops. If a task doesn’t need frame-by-frame precision — a debounced input handler, say — reach for debounce or throttle instead.

requestIdleCallback: only when nothing else is waiting

requestIdleCallback (rIC) takes the opposite approach: instead of running at a fixed point in the frame cycle, it runs only during the gaps — the leftover time in a frame after rendering and other higher-priority work is done, or when the main thread has nothing scheduled at all.

requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    processTask(tasks.pop());
  }
}, { timeout: 2000 });

The callback receives a deadline object with timeRemaining(), telling you how much idle time is actually left in this slice — the expectation is that you do a chunk of work, check the deadline, and stop (yielding the rest to a later idle period) rather than running until you’re done regardless of how long it takes. The optional timeout guarantees the callback still fires eventually even if the browser is never idle, at the cost of potentially interrupting real work to force it through.

This fits work that’s useful but not urgent: pre-fetching data the user probably needs next, logging or analytics batching, warming a cache, or breaking a large computation into chunks so it doesn’t block anything the user is actively doing. It’s explicitly not for anything visual — because idle time is unpredictable, an animation driven by rIC would stutter unpredictably instead of running smoothly.

requestAnimationFrame vs requestIdleCallback

requestAnimationFramerequestIdleCallback
TimingRight before next repaintOnly during idle gaps
FrequencyUp to display refresh rate (~60Hz+)Unpredictable, whenever idle
Guarantees timing?Yes, tied to the render cycleNo, timeout only forces eventual execution
Paused when tab hiddenYesContinues, though idle periods shrink
Best forVisual updates, animation, canvasBackground prefetching, logging, chunked work
Wrong tool forLong-running background tasksAnything the user needs to see change smoothly

Browser support caveat

requestIdleCallback is not implemented in every browser engine — notably, Safari has never shipped it. Code that depends on it for correctness (rather than as a performance nicety) needs a fallback, commonly a setTimeout with a short delay as a rough approximation of idle time. requestAnimationFrame has universal support and doesn’t need one.

How they relate to the rest of the rendering pipeline

Both APIs are about when your code runs relative to the browser’s own work, which is the same concern behind the critical rendering path and Core Web Vitals: a long JavaScript task that blocks the main thread delays paint regardless of which scheduler you used to kick it off. Breaking up expensive work — using rIC’s timeRemaining() to chunk it, or moving it off the main thread entirely with a web worker — is what actually prevents jank; the scheduling API just decides which category of work gets priority.

The takeaway

Use requestAnimationFrame for anything the user sees change — it’s tied to the repaint cycle and runs as smoothly as the display allows. Use requestIdleCallback for background work that’s useful but not urgent, and design it to check timeRemaining() and yield rather than assuming it gets to run to completion. Neither replaces genuinely expensive work being moved off the main thread — they only control when on-thread work gets its turn.

Takina Takina · · 6 min read

How to Break Up Long Tasks in JavaScript

Long tasks block the main thread for 50ms or more and make pages feel frozen. How to split them with yielding, scheduler.yield(), postTask, and workers.

#JavaScript #Performance #Web Development
Takina Takina · · 5 min read

Microtasks vs Macrotasks in JavaScript, Explained

Microtasks (promise callbacks) run before the next macrotask (timers, events). How the two queues are ordered, and why it matters for your code.

#JavaScript #Web Development #Performance
Takina Takina · · 5 min read

OffscreenCanvas API Explained

OffscreenCanvas lets you render canvas graphics off the main thread, in a web worker, so heavy drawing work stops blocking scrolling, input, and animation.

#JavaScript #Web Development #Performance