ブラウザの「隙間」を支配せよ:requestIdleCallbackで実現する極上のUX
やあ。現場でコードを書いていて、こんな悩みを感じたことはないかい?
「メインスレッドが重い処理で埋まって、UIの応答性がガクッと落ちる」
「複雑なDOM操作を無理やり詰め込んで、スクロールがカクつく」
ブラウザは健気だ。16.6ms(60fps)という猛烈なペースで、レイアウト計算、ペイント、合成という「命がけのタスク」をこなしている。そこに僕らが深く考えずに重い処理を投げ込めば、ブラウザは悲鳴を上げ、ユーザーはカクつく画面を目にして離脱する。
今日は、ブラウザの「アイドル時間」をハックして、この非情な現実をねじ伏せるための武器、`requestIdleCallback`について話をしよう。
—
ブラウザの裏側:メインスレッドの「呼吸」を理解する
まず、ブラウザのレンダリングパイプラインを思い出してほしい。HTMLをパースし、DOM/CSSOMを構築し、レンダリングツリーを経て、レイアウトとペイントを行う。これらはすべて「メインスレッド」という、たった一本の細い道を通らなければならない。
この一本道には、実は「フレーム間のわずかな隙間」が存在する。
ブラウザは画面を更新する際、あらかじめ決められたスケジュールの中で、急ぎのタスク(ユーザー入力の反映やアニメーション)を片付けた後、次のフレームを描画するまでの数ミリ秒、暇を持て余すことがあるんだ。
`requestIdleCallback`は、ブラウザに対してこう告げるAPIだ。
「もし今、メインスレッドに余裕があるなら、このタスクを空き時間で実行しておいてくれ」
これがどれほど強力か分かるかい?優先度の低い処理を、レンダリングを阻害しないタイミングまで「先送り」できるんだ。
—
実践:requestIdleCallbackの正しい使い方
ただ、このAPIをそのまま使うのは少し危うい。なぜなら、ブラウザがいつまでも暇になるとは限らないからだ。もし重い処理が終わらなければ、レンダリングを阻害する可能性がある。
だからこそ、現場では「タイムアウト設定」と「タスクの分割」が必須になる。以下のコードを見てほしい。
/
- 非優先タスクをアイドル時間に安全に実行するヘルパー関数
- @param {Function} task – 実行したい重い処理の関数
- @param {number} timeout – 最大で待つミリ秒数
/
const runTaskInIdle = (task, timeout = 2000) => {
const options = { timeout };
// requestIdleCallbackをサポートしていない環境への配慮(setTimeoutで代替)
const scheduler = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
scheduler((deadline) => {
// deadline.timeRemaining() で、現在のフレームで使える残りの時間を取得
// もしくはタイムアウト時間が過ぎた場合も実行する
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && task.length > 0) {
// タスクを細切れにして実行する(ここがポイント)
const subTask = task.shift();
subTask();
}
// まだタスクが残っていれば、次のアイドル時間を待つ
if (task.length > 0) {
runTaskInIdle(task, timeout);
}
}, options);
};
// 使い方:重いDOM操作やデータ整形を配列に詰めて渡す
const heavyTasks = [
() => console.log(“ログの送信処理…”),
() => document.querySelector(‘#heavy-component’).innerHTML = ‘レンダリング完了’,
() => performComplexCalculation(),
];
runTaskInIdle(heavyTasks);
なぜこの実装が「現場レベル」なのか
1. タイムアウトの活用: `deadline.didTimeout` をチェックしているのが肝だ。ブラウザが忙しすぎてアイドル時間がずっと来なくても、一定時間が経過すれば強制的に実行する。これにより、ユーザー体験を損なわない範囲での「最低限の責任」を果たせる。
2. タスクの分割: `task.shift()` を使って処理を細分化している。もしタスクが一つでも数ミリ秒を超えてしまうと、次のフレームの描画を遅らせてしまう。処理は「刻む」のが鉄則だ。
3. 互換性の担保: `requestIdleCallback` は Safari などで未サポートの場合がある。簡易的なフォールバックを組み込んでおくのが、プロとしてのたしなみだよ。
—
最後に:魔法ではないという自覚を
`requestIdleCallback`は万能薬じゃない。あくまで「ブラウザの善意」に依存する仕組みだ。
- DOM操作には注意: アイドル時間中に DOM をゴリゴリ書き換えると、結局次のフレームで再レイアウトが走り、パフォーマンスを悪化させることもある。あくまで「データの準備」や「ロギング」「分析送信」といった、描画に直接影響しない部分に使うのがセオリーだ。
Webブラウザという巨大で複雑な機械の上で、僕らができることは限られている。だが、その数ミリ秒の「隙間」を意識するだけで、君が書くアプリケーションの品格は一段階上がるはずだ。
さあ、コードを開いて、メインスレッドを解放してやってくれ。健闘を祈るよ。

コメント