· Tech  · 7 min read

requestAnimationFrame (rAF) and requestIdleCallback (rIC)

A practical guide to JavaScript's render and idle phases, with real-world examples and code walkthroughs.

A practical guide to JavaScript's render and idle phases, with real-world examples and code walkthroughs.

In previous post, we explored the event loop in JavaScript. There are two more important steps or phases in the loop.

  1. Render phase, where you can queue a callback using requestAnimationFrame.
  2. Idle period, where you can queue a callback using requestIdleCallback.

requestAnimationFrame

requestAnimationFrame, aka rAF, is just like queueMicrotask—a way to queue a callback to a specific phase within the JavaScript event loop.

The callback registered will most likely run right after all microtasks are flushed, although the timing can depend on what the browser or engine is processing at the time. Also, it will not be called when the tab is inactive. The rule of thumb is that it is called right before the render phase, to do anything related to rendering or animation.

requestIdleCallback

Just like rAF, rIC registers a callback for the idle phase. If a browser is too busy, this callback might not be called.

Just like rAF, rIC registers a callback for the idle phase. If the browser is too busy, this callback might not be called.

Example for Both

Disclaimer: This specific example code was aided by AI, after discussing the article with it, it voluntarily generated an example, and I edited it with it. Disclaimer: This example code was generated with the help of AI after discussing the article, and then edited collaboratively.

console.log("--- 1. Start the Script (Macrotask 1) ---");

// 1. Macro Task (For Next Loop)
setTimeout(() => {
  console.log("--- 5. setTimeout (Macrotask 2) ---");
}, 0);

// 2. Micro Tasks
queueMicrotask(() => {
  console.log("--- 2. queueMicrotask (Microtask) ---");
  // Nested microtask
  queueMicrotask(() => {
    console.log("--- 3. Nested queueMicrotask (Microtask) ---");
  });
});

// 3. Render Step
requestAnimationFrame((time) => {
  console.log("--- 4. requestAnimationFrame (Render Step) ---");
});

// 4. Idle Period
const idleCallbackId = requestIdleCallback(
  (deadline) => {
    console.log("--- 6. requestIdleCallback (Idle Period) ---");
    // More on the deadline later
    console.log(`   Time remaining: ${deadline.timeRemaining().toFixed(2)}ms`);
  },
  { timeout: 20000 }
);

requestAnimationFrame callback with argument

Actually p5.js uses this under the hood. Here are some bullet points you can take notes:

  1. It’s called during the render phase, which happens every 16.7 ms, and when there are no microtasks, and the tab is active.
  2. Frequency depends on the refresh rate; it’s roughly 60 times per second, but if you have a gaming display of 144Hz, then it’s called 144 times per second.
  3. When the tab is inactive, it is not called.
  4. When a phone or laptop is in low energy mode, its frequency could be reduced by half.
  5. The order is rAF → Parse Style → Construct Layout → Paint.
  6. The callback is called with an argument called timestamp—DOMHighResTimeStamp, which is the time elapsed since the script loaded.
  7. requestAnimationFrame returns an ID with which you can cancel the callback via cancelAnimationFrame, but you probably won’t need it.

Example:

let count = 0;
let lastCountTimestamp = 0;

function logCount(time) {
  console.log("time is: ", time);
  if (time > 1000 * 10) {
    // More than 10 seconds have passed
    return;
  }

  if (time - lastCountTimestamp >= 50) {
    console.log(count++);
    lastCountTimestamp += 50;
    requestAnimationFrame(logCount);
    return;
  }

  console.log("50ms has not passed; not counting");
  // If 50ms has not passed, do not count

  requestAnimationFrame(logCount);
}

requestAnimationFrame(logCount);

requestIdleCallback callback and options

So what is an IDLE callback? Can the browser be idling? Actually it can. The browser renders, you wait, then you read, the browser gets idling. requestIdleCallback can register a callback that is executed when the browser is in that idle phase, and depending on how much task the browser is having, its execution is NOT guaranteed. If the tab is inactive, or too busy, it is not called at all.

  1. This is used for callbacks where frequency is not important or less critical, such as posting logs to a logger.
  2. If you want it to be definitely called, you can pass the options (like the sample above) to specify the timeout in milliseconds. Once this is reached, the browser is forced to invoke the callback.
  3. If you do not provide options, it can be forgotten forever if the browser is super busy.
  4. The callback is given an argument called deadline; it’s an object with two APIs: timeRemaining (method that returns milliseconds until the deadline), and didTimeout (boolean).
  5. requestIdleCallback returns an ID with which you can cancel the callback via cancelIdleCallback.
// Sample: Using deadline, didTimeout, and cancelIdleCallback
// Line-by-line explanation:
// 1. Declare a variable to store the callback ID returned by requestIdleCallback.
let idleCallbackId;

// 2. Define the callback function that will be called during the browser's idle period.
function handleIdle(deadline) {
  // 3. Check if the callback was triggered due to timeout.
  if (deadline.didTimeout) {
    console.log("Callback was triggered by timeout!");
  } else {
    // 4. Otherwise, log the remaining idle time in milliseconds.
    console.log(`Time remaining: ${deadline.timeRemaining().toFixed(2)}ms`);
  }

  // 5. If the remaining idle time is less than 5ms, cancel the callback.
  if (deadline.timeRemaining() < 5) {
    cancelIdleCallback(idleCallbackId);
    console.log("idleCallback was cancelled");
  }
}

// 6. Register the callback with a timeout option (100ms). The returned ID is stored for cancellation.
idleCallbackId = requestIdleCallback(handleIdle, { timeout: 100 });

業界的な位置づけ requestIdleCallback(rIC)は、ブラウザが「暇なとき」にだけ呼ばれるコールバックを登録するAPIです。 「暇なとき」とは、レンダリングやイベント処理などの重要な仕事が終わった後、CPUがちょっと息抜きしているタイミング。 つまり、火星でジャガイモを育てる合間に、ちょっとした雑用を片付ける感じです。 主な特徴・注意点 rICのコールバックは、ブラウザが忙しすぎると呼ばれないことがあります。 たとえば、タブが非アクティブだったり、CPUがフル稼働しているときは「また今度ね」とスルーされる。 重要な処理やUI更新には絶対使わないこと。あくまで「余裕があればやる」系の仕事専用。 options の timeout 設定 rICは第二引数で options を渡せます。ここで timeout を指定すると「どんなに忙しくても、この時間が経ったら絶対呼んでね」とお願いできます。 ただし、timeoutはミリ秒単位。指定しないと、永遠に呼ばれない可能性も。 deadline API コールバックの引数 deadline には、2つの便利なAPIがあります。 deadline.timeRemaining():このidle期間であと何ミリ秒使えるか教えてくれる。火星の酸素残量みたいなもの。 deadline.didTimeout:timeout指定で呼ばれた場合は true になる。つまり「もう待てないから今やるぞ!」という合図。 箇条書きまとめ rICは「暇なときだけ」呼ばれる ブラウザが忙しいと呼ばれない(火星嵐の日は作業できない) options.timeoutで「絶対呼んで!」とお願いできる deadline.timeRemaining()で残り時間を確認できる deadline.didTimeoutで「待ちきれず呼ばれた」か判別できる 「requestIdleCallbackは、火星のジャガイモ畑で暇を見つけて雑用を片付けるAPI。忙しいときは無視されるけど、timeoutを指定すれば『もう待てない!』と強制的に呼ばれる。残り時間も教えてくれるので、酸素残量を気にしながら作業するマーシャンの気分で使いましょう。」

Back to Blog

Related Posts

View All Posts »
JavaScript Event Loop

JavaScript Event Loop

Understanding how JavaScript handles asynchronous operations through the Event Loop, Macro Stack, and Micro Stack mechanisms.

React: Why Prop Change Does NOT Cause Re-render

React: Why Prop Change Does NOT Cause Re-render

A relatively deep dive into React's re-render mechanics, why prop changes alone don't trigger re-renders, and how state libraries like Jotai and Recoil manage surgical updates. Includes step-by-step code and practical patterns.