はじめに
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 に渡して同期させる必要があります。
- 画面に影響しない非同期タスクは
requestIdleCallbackに逃がす - タイムアウト値を設定し、処理の完全な放置を防ぐ
- コールバック内ではDOMを書き換えず、純粋なデータ処理に留める
これらを意識し、ブラウザの処理負荷を分散させることで、常にサクサク動作する最高精度のWebパフォーマンスを実現しましょう。
