Featured image of post 描画を妨げない処理実行:requestIdleCallbackの使いどころ Featured image of post 描画を妨げない処理実行:requestIdleCallbackの使いどころ

描画を妨げない処理実行:requestIdleCallbackの使いどころ

requestIdleCallbackを使った描画を妨げないバックグラウンド処理の実装方法を解説。アナリティクス送信やログ記録など緊急性の低いタスクをブラウザの空き時間に実行し、UIの応答性とフレームレートを維持するパフォーマンス最適化テクニックを紹介します。

はじめに

JavaScriptでWebアプリケーションを構築する際、ユーザーの入力応答やアニメーション(毎秒60フレームの描画)を滑らかに維持することは、UX向上において極めて重要です。

しかし、実際のアプリケーションでは、アナリティクス(アクセス解析)のデータ送信、ユーザーログの出力、ローカルキャッシュの同期、あるいはチャット履歴の事前読み込みといった、「今すぐ実行しなくてもよいが、いつかは実行しておきたい処理」 が多数存在します。

これらを通常の非同期処理(PromiseやsetTimeoutなど)として不用意に呼び出すと、描画フレームの最中にスレッドがブロックされ、画面のガタつき(ジャンク)やINP(Interaction to Next Paint)の悪化を招く原因になります。

この問題を解決するAPIが requestIdleCallback です。本記事では、このAPIの仕組みと実践的なユースケースを解説します。


1. requestIdleCallback とは?

requestIdleCallback は、「ブラウザが描画処理やユーザー入力をすべて終え、メインスレッドが一時的に空いた時間(アイドル時間)」に、優先度の低い処理を実行するようにスケジュールできるAPIです。

ブラウザの描画サイクルのイメージ

ブラウザは通常、1フレーム(約16.6ms)の間に以下の処理を繰り返しています。

[1フレーム (約16.6ms)]
 ├─► 1. ユーザー入力イベント処理
 ├─► 2. 定期タイマー処理 (setTimeout 等)
 ├─► 3. レンダリング前の調整 (requestAnimationFrame)
 ├─► 4. レイアウト計算 & 再ペイント
 └─► 5. 【アイドル時間 (空き)】 ◄── ここで requestIdleCallback を実行!

もし、フレームの最後に数ミリ秒の「空き時間(Idle)」が余っていれば、ブラウザはその時間を使って requestIdleCallback に登録された処理を少しずつ進めます。ユーザー入力などの高優先度イベントが割り込んできた場合は、そちらの処理を即座に最優先させます。


2. 基本的な実装方法

基本的な書き方

// アイドル時間に行いたい優先度の低いタスク
function sendAnalyticsData(deadline) {
  // deadline.timeRemaining() は、現在のフレームで残されている空き時間(ミリ秒)を返す
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift();
    executeTask(task);
  }

  // まだタスクが残っている場合は、次のアイドル時間に繰り越す
  if (tasks.length > 0) {
    requestIdleCallback(sendAnalyticsData);
  }
}

// 実行のスケジュール
requestIdleCallback(sendAnalyticsData);

タイムアウト(timeout)オプションの指定

ブラウザが極端に忙しい状態が続くと、requestIdleCallback に登録した処理がいつまでも実行されない(スターベーション現象)危険性があります。

これを防ぐために、timeout パラメータを指定し、「どれだけ忙しくても、指定ミリ秒が経過したら強制的に処理を実行する」というセーフティネットを張ることができます。

// 最大2秒(2000ms)待つが、それを過ぎたらアイドル時間でなくても強制実行する
requestIdleCallback(sendAnalyticsData, { timeout: 2000 });

3. 実践:requestIdleCallback の主な使いどころ

以下のような「非ビジュアル(画面に即座に影響しない)タスク」に対して絶大な効果を発揮します。

① アナリティクスデータのバッチ送信

ユーザーのクリック追跡やスクロール深度などのアクセス解析イベントを、即座に送信せず、バックグラウンドの空き時間にまとめて送信します。

② ローカルキャッシュやインデックスの同期

IndexedDB や localStorage へのデータの書き込み、不要キャッシュのパージなど、UIスレッドをブロックしたくないデータ同期処理に最適です。

③ コンポーネントやアセットのプリフェッチ

次にユーザーがクリックしそうなページ用リンクのJavaScriptファイルや画像を、アイドル時間にバックグラウンドで先読み(Prefetch)しておきます。


まとめと注意点

requestIdleCallback を利用する際の最大の注意点は、「アイドル処理のコールバック関数内で、DOMを直接操作(書き換え)してはいけない」 という点です。

コールバック内でDOMを変更すると、その瞬間にブラウザはレイアウトの再計算を余儀なくされ、せっかく確保したアイドル時間が相殺されてパフォーマンス低下に直結します。DOMの書き換えを行いたい場合は、処理部分のみ requestAnimationFrame に渡して同期させる必要があります。

  1. 画面に影響しない非同期タスクは requestIdleCallback に逃がす
  2. タイムアウト値を設定し、処理の完全な放置を防ぐ
  3. コールバック内ではDOMを書き換えず、純粋なデータ処理に留める

これらを意識し、ブラウザの処理負荷を分散させることで、常にサクサク動作する最高精度のWebパフォーマンスを実現しましょう。