INPという「現場の審判」に抗う——ブラウザの深淵からパフォーマンスをハックする
「ボタンを押したのに、何かが重い」。
ユーザーが抱くこの小さな違和感こそが、Webアプリケーションの死因です。かつての指標であるFID(First Input Delay)が「最初の入力」という限定的な瞬間を切り取っていたのに対し、INP (Interaction to Next Paint) は、ユーザーがWebサイトで過ごす全期間の「応答性」を容赦なく暴き出します。
上級エンジニアである諸君なら既に理解しているはずだ。ブラウザのメインスレッドは、常に「たった一人で戦う孤独なランナー」だということを。HTMLのパース、CSSの計算、JSの実行、そしてレイアウトとペイント。この過酷なメインスレッド上で、INPを改善するということは、すなわち「タスクの渋滞をいかにして回避するか」という交通整理の技術に他ならない。
1. 「遅延」の正体:なぜメインスレッドは止まるのか
INPの計測における本質的な敵は、長いタスク(Long Tasks)です。
ブラウザは一度JSの実行を開始すると、それが終わるまでレンダリングの更新を後回しにします。もし君たちが書いたイベントハンドラが重いDOM操作や複雑な計算を含んでいれば、ブラウザは次のフレームを描画する権利を奪われます。これがINP悪化の直接的な原因です。
避けられない「競合」をどう制御するか
例えば、ユーザーのクリックをトリガーに巨大なリストをレンダリングする場合を考えてみよう。
// 悪い例:イベントハンドラ内で重い処理を同期的に実行
button.addEventListener(‘click’, () => {
// DOM構築と計算がメインスレッドを長時間占有する
const data = heavyDataProcessing();
updateUI(data);
// ここで初めてレンダリングへ移行できるが、既にINPは超過している
});
この「同期的な実行」を断ち切るために、我々が取るべき手段はタイムスライシング(Time Slicing)です。`requestIdleCallback`や`setTimeout`を使い、処理をメインスレッドの隙間に滑り込ませる知恵が必要です。
2. INPを最適化するための「防衛的アーキテクチャ」
INPを改善するための究極の処方箋は「レンダリングに必要な最小限の処理以外を、すべてメインスレッドから追い出すこと」です。
非同期処理の賢い分割術
タスクを分割し、ブラウザが「呼吸する時間」を与える。これが現場で最も効果を発揮する最適化手法です。
/
- 非同期で処理を分割するユーティリティ
- メインスレッドを解放し、ブラウザの描画を優先させる
/
const yieldToMain = () => {
return new Promise((resolve) => {
// 次のタスクとして実行されるように非同期の隙間を作る
setTimeout(resolve, 0);
});
};
button.addEventListener(‘click’, async () => {
// 1. UIの応答を優先(ボタンのホバー状態解除やフィードバック)
updateButtonState(‘loading’);
// 2. 処理を細分化する
await yieldToMain();
// 3. 重い計算処理を再開
const result = performHeavyCalculation();
await yieldToMain();
// 4. 最終的な描画
renderResult(result);
});
3. メモリとレンダリングの「見えざる足かせ」
上級エンジニアが見落としがちなのが、ガベージコレクション(GC)の暴走です。
不必要なオブジェクト生成を繰り返すと、ブラウザは「計算」ではなく「メモリの掃除」にリソースを割き始めます。メモリ不足によるGCの発生は、予測不可能なメインスレッドの停止を招き、INPのスコアを予測不能なほど悪化させます。
- バッファの再利用: `Array`や`Object`の生成を減らし、可能な限り既存のメモリ空間を使い回す。
- レイアウトスラッシングの回避: 読み取り(`offsetHeight`等)と書き込み(`style.width`等)を交互に行うコードは、ブラウザに強制的なレイアウト計算(リフロー)を強いる。これはINPの天敵だ。
4. 現場で使える「現実的なバグ回避」の鉄則
INPを計測・改善する際、以下の3つを現場の憲法として定めてほしい。
1. 入力のバリデーションは「入力」より「後」に: ユーザーのタイピングに対して即座に重いバリデーションを走らせない。`debounce`や`requestAnimationFrame`を活用し、描画の切れ目まで待機させる。
2. Web Workerの積極活用: 「UIと関係ない計算」はすべてWorkerへ移譲する。メインスレッドを「DOMを操作するだけの贅沢な場所」に保て。
3. ロングタスクの可視化: `PerformanceObserver` APIを使い、現場でどの処理が50msを超えているかを常に監視する。
// ロングタスクの監視(開発環境での計測に有用)
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(`[Long Task Detected]: ${entry.duration}ms`, entry);
}
});
observer.observe({ entryTypes: [‘longtask’] });
結論:ブラウザを「制御」するのではなく「調和」させる
INPの最適化は、単なる数値ゲームではない。それは、ブラウザという極めて複雑で人間味のある(気まぐれな)仕組みに対して、敬意を払い、そのリズムに合わせてコードを流し込むという、芸術に近い営みです。
ブラウザのメインスレッドは、君たちが書いたコードが「いつ」「どのくらい」止めるかを知っている。その呼吸を読み、最適解を導き出した時、Webアプリケーションは初めて「サクサク動く」という体験を手に入れる。
さあ、計測ツールを回し、メインスレッドの渋滞を可視化しよう。君たちのアプリケーションが、ユーザーの指先一つで淀みなく動く未来を目指して。

コメント