フロントエンドの現場で、「なぜかUIがカクつく」「スクロールが重い」という相談を受けたとき、真っ先に疑うべきはメインスレッドの「過密スケジュール」です。
ブラウザのレンダリングエンジンは、16.6ms(60fps)という過酷な締め切りの中で、HTMLのパース、CSSOMの構築、レイアウト計算、そしてペイントという重労働をこなしています。ここに、「ついでにこれもやっておいて」という重いJavaScriptタスクを突っ込めば、当然のごとくフレームドロップが発生し、ユーザーは「このサイト、重いな」と不快感を感じるわけです。
今日は、そんなメインスレッドの過密スケジュールに少しだけ余裕を作るための切り札、`requestIdleCallback` について、現場の視点から深掘りしていきましょう。
—
1. ブラウザの「隙間時間」を見極める
ブラウザのメインスレッドは、実は常にフル稼働しているわけではありません。レンダリングのパイプラインやJavaScriptの実行が一段落した後の「空き時間(Idle Time)」が存在します。
`requestIdleCallback` は、ブラウザに対し「メインスレッドが暇になったら、このタスクを実行してくれ。ただし、もしブラウザが忙しくなったら中断してもいいよ」と予約を入れるためのAPIです。
このAPIの肝は、「重要ではないタスクを、ユーザー体験を損なわずに裏で処理する」という点にあります。例えば、以下のようなタスクには最適です。
- 分析用データの送信(トラッキングなど)
- 画面に表示されていないコンポーネントの事前データ取得
- 長大なリストのレンダリングの分割
- 非表示領域のDOM構築・整理
—
2. 実践:requestIdleCallback の賢い使い方
ただ闇雲に突っ込めばいいわけではありません。このAPIは、コールバック関数に `IdleDeadline` というオブジェクトを渡してくれます。これを使うことで、現在の隙間時間がどれくらい残っているかを判断し、タスクを安全に分割できます。
現場で使える汎用的なタスクスケジューラーの例を見てみましょう。
/
- 巨大なタスクをブラウザのアイドル時間を利用して分割実行する
- @param {Function[]} tasks – 実行したいタスクの配列
/
function processTasksInIdle(tasks) {
let index = 0;
const workLoop = (deadline) => {
// timeRemaining() は、現在のアイドル時間が残り何ミリ秒かを示す
// 少なくとも1msの余裕があればタスクを一つ消化する
while (deadline.timeRemaining() > 1 && index < tasks.length) {
tasks[index]();
index++;
}
// まだタスクが残っていれば、次のアイドル時間を予約する
if (index < tasks.length) {
requestIdleCallback(workLoop);
} else {
console.log('すべてのタスクが完了しました');
}
};
// ブラウザに実行を予約
requestIdleCallback(workLoop);
}
// 使用例:大量のDOM操作を伴う処理を分割してみる
const heavyTasks = Array.from({ length: 100 }, (_, i) => () => {
const div = document.createElement(‘div’);
div.textContent = `アイテム ${i}`;
document.body.appendChild(div);
});
processTasksInIdle(heavyTasks);
—
3. シニアからの重要な警告:注意すべき落とし穴
このAPIは魔法の杖ではありません。以下の点には十分注意してください。
- DOM操作のタイミングに注意: `requestIdleCallback` の中でDOMを書き換えると、スタイル計算やレイアウト(Reflow)が後回しになり、結局フレームの境界をまたいでしまう可能性があります。Reactのように仮想DOMを扱うライブラリならまだしも、生のDOM操作を過度に行うと逆にパフォーマンスを悪化させます。
- タイムアウトを適切に設定: `requestIdleCallback` には第2引数でオプションを渡せます。`{ timeout: 2000 }` のように設定しておけば、どれだけメインスレッドが混雑していても、最大2秒後には強制的に実行されます。これを入れておかないと、極端な高負荷環境でタスクが永遠に実行されないリスクがあります。
- アニメーションには使わない: 画面の変化を伴う処理(`requestAnimationFrame` を使うべき場面)でこれを使うと、UIの同期が取れず、不自然な挙動になります。あくまで「裏方仕事」専用です。
—
現場の知恵:なぜこれをやるのか?
結局のところ、僕たちが目指すべきは「ユーザーにストレスを感じさせないこと」です。
ユーザーは、ボタンを押した瞬間の反応の速さを期待しています。その裏で、解析用スクリプトがCPUを占有してボタンの反応を0.1秒遅らせるような事態は、プロとして避けなければなりません。
`requestIdleCallback` は、ブラウザという「呼吸をしている機械」の隙間を縫うようにタスクを差し込む、いわば「行儀の良いプログラミング」です。皆さんのプロジェクトでも、まずは「すぐにやる必要はないけれど、いつかはやらないといけない処理」をリストアップして、この仕組みに置き換えるところから始めてみてください。
ブラウザの挙動を深く理解し、そのリソースを最大限に尊重する。それこそが、パフォーマンス・チューニングの真髄であり、一流のフロントエンドエンジニアへの第一歩です。困ったことがあれば、またいつでも聞いてください。

コメント