INPのプレゼンテーション遅延: DOMサイズ、レイアウト処理、レンダリングの最適化
プレゼンテーション遅延によって引き起こされるINPの問題を特定し、改善する方法を学びましょう

プレゼンテーション遅延によって引き起こされるInteraction to Next Paint (INP)の問題
このページは、当社のInteraction to Next Paint (INP)シリーズの一部です。INPは、ユーザーのインタラクションから次の視覚的な更新までの合計時間を測定します。プレゼンテーション遅延は、入力遅延および処理時間に続く、INPの3番目かつ最後のフェーズです。INPを初めて学ぶ場合は、まずINPの問題を特定して修正する方法に関するガイドをお読みください。
要約すると、Interaction to Next Paint (INP) は、ユーザーがページとやり取りした後に視覚的な変化を見るまでにかかる時間を測定します。このINPは、「入力遅延」、「処理時間」、「プレゼンテーション遅延」の3つのコンポーネントに分類できます。
プレゼンテーション遅延は合計INPの最大の要因であり、平均して合計INP時間の約42%を占めます。レンダリングパイプラインを最適化し、HTML構造を簡素化することが、INPを改善するための最も大きな手段です。
プレゼンテーション遅延: ボタンをクリックしたとき、結果が表示されるまでにほんの一瞬時間がかかりすぎると疑問に思ったことはありませんか?それがInteraction to Next Paint (INP)の実際の動きです。プレゼンテーション遅延はインタラクションプロセスの最後のステップであり、クリックが処理された後、視覚的な変化を見る前に発生します。
Table of Contents!
プレゼンテーション遅延を理解する
プレゼンテーションはインタラクションの最終フェーズです。プレゼンテーション遅延は、インタラクションに続く視覚的な更新をブラウザがレンダリングするのにかかる時間を表します。プレゼンテーション遅延は、インタラクションのイベントハンドラーの実行が終了したときに始まり、次のフレーム(視覚的な変化を含む)がペイントされたときに終わります。プレゼンテーション遅延は、レイアウトの複雑さ、DOMのサイズ、必要なレンダリング処理の量など、さまざまな要因の影響を受ける可能性があります。

Interaction to Next Paint (INP) は、「入力遅延」、「処理時間」、「プレゼンテーション遅延」の3つのサブパートに分類できます。
プレゼンテーション遅延とINP
プレゼンテーション遅延はINPの最後のフェーズです。平均して、プレゼンテーション遅延は合計INP時間の約42%を占めており、インタラクションが遅くなる最大の要因となっています。

CoreDashでは、1時間ごとに何百万ものCore Web Vitalsデータポイントを収集しています。そのデータに基づくと、プレゼンテーション遅延はInteraction to Next Paintの42%を占めています。これは、処理時間(40%)よりも多く、入力遅延(18%)よりも大幅に多くなっています。最大の要因であるにもかかわらず、アプリケーションコードではなくブラウザのレンダリングパイプラインが関与するため、プレゼンテーション遅延は多くの場合最適化が最も難しいフェーズです。
プレゼンテーション遅延の例: スマートフォンでeコマースサイトを見て、新しい靴を探していると想像してください。製品画像をタップして詳細を表示しようとします。しかし、お使いのスマートフォンは少し古く、処理に苦労しています。 画像をタップします(インタラクション)。スマートフォンがリクエストを処理し、表示を更新するのに少し時間がかかります(処理時間)。Webサイトは、より大きな画像と詳細情報を含む新しいページをレンダリングする必要があります。最後に、新しい製品の詳細と画像が画面に表示されるまでに顕著な時間がかかります(プレゼンテーション遅延)。INPにおけるこの遅延はユーザーにとってイライラの原因となる可能性があり、だからこそこれを修正することが重要です。
プレゼンテーション遅延が長くなる原因は何か?
プレゼンテーション遅延には、イベントハンドラーが終了した後、ピクセルが画面に表示される前にブラウザが行うすべての処理が含まれます。これには、スタイルの再計算、レイアウトの計算、ペイント、合成が含まれます。プレゼンテーション遅延が長くなる原因には、いくつかの要因があります。
大きなDOMサイズ
大規模または深くネストされたDOMは、プレゼンテーション遅延が長くなる最も一般的な原因の1つです。インタラクション後にページを視覚的に更新する必要があるたびに、ブラウザはスタイルを再計算し、レイアウトを計算し、影響を受ける要素を再ペイントする必要があります。これらの各ステップのコストは、影響を受けるDOMノードの数に比例して増加します。
Googleは、DOMを1,400要素未満に抑え、最大の深さを32レベルに、親ノードあたりの子要素を60個以内にすることを推奨しています(LighthouseのDOMサイズ監査を参照)。DOMがこれらのしきい値を超えると、ブラウザは各インタラクション後のスタイルの再計算とレイアウトの計算にはるかに多くの時間を費やします。
次のようなシナリオを考えてみましょう。ユーザーがボタンをクリックして、コンテナ要素のCSSクラスを切り替えます。そのコンテナに5,000個の子孫ノードがある場合、実際に視覚的に変化する要素が少数であっても、ブラウザは潜在的にすべての要素のスタイルを再計算する必要があります。このスタイルの再計算は次のペイントの前に同期的に発生し、プレゼンテーション遅延を直接増加させます。
DOMを削減するための具体的なテクニックについては、過大なDOMサイズの修正に関するガイドをお読みください。
過剰なレイアウト処理
レイアウト(「リフロー」とも呼ばれます)は、ブラウザがページ上のすべての表示要素の位置と寸法を計算するプロセスです。DOMを変更したり、ジオメトリ(width、height、margin、padding、top、left)に影響を与えるCSSプロパティを変更したりするインタラクションの後、ブラウザは更新されたフレームをペイントする前にレイアウトを実行する必要があります。
以下の2つのパターンは、プレゼンテーション遅延に特に悪影響を及ぼします。
強制同期レイアウト(Forced synchronous layout)は、レイアウトを無効にするDOMの変更を行った後、JavaScriptがレイアウトプロパティ(offsetHeightやgetBoundingClientRect()など)を読み取るときに発生します。ブラウザは正確な値を返すために、イベントハンドラー内で同期的にレイアウトを実行することを余儀なくされます。このレイアウト処理はその後処理時間の一部になりますが、さらにDOMが変更されることによってトリガーされる後続のレイアウトはプレゼンテーション遅延の一部になります。
レイアウトスラッシング(Layout thrashing)は、DOMに書き込み、その後ループ内でレイアウトプロパティを読み取るという繰り返しのパターンです。読み取りのたびにブラウザはレイアウトの再計算を強制され、書き込みのたびにレイアウトが再び無効になります。これにより、1回のインタラクションで数十回、あるいは数百回の不要なレイアウト計算が発生する可能性があります。以下はレイアウトスラッシングの例と、その修正方法です。
// BAD: ループ内でのレイアウトスラッシング
function resizeItems() {
const items = document.querySelectorAll('.item');
items.forEach(item => {
// 読み取り(レイアウトを強制)
const parentWidth = item.parentElement.offsetWidth;
// 書き込み(レイアウトを無効化)
item.style.width = parentWidth + 'px';
});
}
// GOOD: 読み取りをバッチ処理し、その後書き込みをバッチ処理する
function resizeItems() {
const items = document.querySelectorAll('.item');
// 最初にすべての値を読み取る
const widths = Array.from(items).map(
item => item.parentElement.offsetWidth
);
// その後すべての値を書き込む
items.forEach((item, i) => {
item.style.width = widths[i] + 'px';
});
} シングルページアプリケーション(SPA)におけるクライアントサイドレンダリング
HTMLのクライアントサイドレンダリングは、特にシングルページアプリケーション(SPA)において、プレゼンテーション遅延に大きな影響を与える可能性があります。ユーザーのインタラクションによってルートの変更や大規模なUIの更新がトリガーされた場合、SPAフレームワークは次のことを行う必要があります。
- 仮想DOMの差分(diffing)アルゴリズムを実行して、何が変更されたかを判断する
- 結果として生じたDOMのミューテーションを実際のDOMに適用する
- 影響を受けるすべての要素に対して、スタイルの再計算とレイアウトをトリガーする
- 更新されたフレームをペイントする
Reactアプリケーションでは、仮想DOMの差分調整(reconciliation)プロセスは処理時間の一部ですが、結果として生じるDOMのミューテーションとそのレンダリングコストはプレゼンテーション遅延に分類されます。コンポーネントツリーが生成するDOMノードが多いほど、差分調整とその後のレンダリング処理のコストが高くなります。
ReactおよびNext.jsアプリケーションでこれを軽減するには:
React.memo()を使用して、同じpropsを受け取る子コンポーネントの不要な再レンダリングを防ぎます。- コストの高い再レンダリングをトリガーする値には
useDeferredValue()を使用し、Reactがより緊急性の高い更新を優先できるようにします。 - コンポーネントツリーを浅く保ちます。深くネストされたコンポーネント階層は深くネストされたDOMを生成し、差分調整とブラウザレンダリングの両方のコストを増加させます。
- 長いリストには仮想化ライブラリ(
react-windowや@tanstack/react-virtualなど)を使用し、DOMに表示されているアイテムのみが含まれるようにします。
プレゼンテーション遅延を削減する
DOMサイズを最小限に抑える
プレゼンテーション遅延にとって最大のメリットは、DOMを小さく保つことです。
- 未使用のHTML要素、特に深くネストされたラッパー用のdivを削除します。
- 長いリストにはリストの仮想化を使用します(表示されているアイテムと小さなバッファのみをレンダリングします)。
- 可能な場合は、深くネストされた構造を平坦化します。
- レイアウトにネストされたdivの代わりにCSS GridとFlexboxを使用します。
// DOMサイズを縮小するために長いリストを仮想化する
// 改善前: DOMに10,000個のアイテム
<ul>
{allItems.map(item =>
<li key="{item.id}">{item.name}</li>)
}
</ul>
// 改善後: DOMには表示されているアイテムのみ(react-windowを使用)
import { FixedSizeList } from 'react-window';
<fixedsizelist height="{600}"
itemcount="{allItems.length}"
itemsize="{50}" width="100%">
{({ index, style }) => (
<div style="{style}">{allItems[index].name}</div>
)}
</fixedsizelist>
content-visibilityを使用して画面外のコンテンツを遅延レンダリングする
CSSのcontent-visibilityプロパティは、ユーザーがその近くにスクロールするまで、画面外のコンテンツのレンダリングをスキップするようにブラウザに指示します。これにより、スタイルの再計算とレイアウトの範囲がページの表示部分に制限されるため、インタラクション中のレンダリング処理量が削減されます。
/* ファーストビュー以下のセクションにcontent-visibilityを適用する */
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
/* 長いリストの個々のアイテムに適用する */
.list-item {
content-visibility: auto;
contain-intrinsic-size: auto 80px;
} contain-intrinsic-sizeプロパティは推定の高さを提供するため、ブラウザはコンテンツをレンダリングしなくてもスクロールバーのサイズを正しく計算できます。これにより、ユーザーがスクロールしてコンテンツが表示される際のレイアウトシフトを防ぐことができます。
レンダリングコストを削減するその他のCSS最適化戦略については、未使用のCSSの削除に関するガイドを参照してください。
インタラクションによってトリガーされるレイアウト処理を最小限に抑える
インタラクションを設計する際は、レイアウトをトリガーしないCSSプロパティを優先してください。transformやopacityなどのプロパティは、レイアウトやペイントをトリガーすることなくGPUコンポジターで処理できます。top、left、width、heightをアニメーション化する代わりに、transform: translate()やtransform: scale()を使用します。レイアウトをトリガーするCSSプロパティの完全なリストについては、web.devのレンダリングパフォーマンスガイドを参照してください。
CSSのwill-changeプロパティを使用して、要素がアニメーション化されることをブラウザにヒントとして伝えます。これにより、ブラウザはその要素に対して別個のコンポジターレイヤーを作成し、ページの他の部分からレンダリングを分離することができます。
/* 要素を独自のコンポジターレイヤーに昇格させる */
.animated-element {
will-change: transform, opacity;
}
/* displayの代わりにopacityを使用して表示を切り替える */
.modal {
opacity: 0;
pointer-events: none;
transform: translateY(10px);
transition: opacity 0.2s, transform 0.2s;
}
.modal.active {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
} 長いプレゼンテーション遅延を特定する
長いプレゼンテーション遅延を特定するには、Chromeのパフォーマンスプロファイラーを使用できます。DevTools(Ctrl+Shift+I)を開き、Performanceタブに移動して録画ボタンを押し、ページとやり取りします。
その後、インタラクションのタイムラインを分析し、プレゼンテーション遅延を含むさまざまなフェーズを視覚化できます。イベントハンドラーが終了した後に発生するレンダリングの更新を調べることで、長いプレゼンテーション遅延の原因となっているボトルネックを特定できます。タイムラインで「Recalculate Style」、「Layout」、「Paint」の大きなエントリを探してください。これらは、プレゼンテーション遅延のフェーズ中にブラウザが行う処理を表しています。

RUMデータを使用してプレゼンテーション遅延を特定する
Long Animation Frames (LoAF) を使用してプレゼンテーション遅延を測定する
Long Animation Frames (LoAF) APIは、ユーザーのインタラクション中にレンダリング遅延を引き起こす原因を正確に示します。このAPIは、処理時間とプレゼンテーション遅延を分離し、どのスクリプトがレンダリングを遅くしているかを特定するためのタイミングデータを提供します。
プレゼンテーション遅延を理解するための重要なLoAFプロパティは次のとおりです。
renderStart: ブラウザがレンダリングフェーズ(スタイルの再計算、レイアウト、ペイント)を開始した時間styleAndLayoutStart: スタイルとレイアウトの計算が開始された時間duration: long animation frameの合計継続時間blockingDuration: フレームがスクリプトによってブロックされた時間
LoAFは現在Chromium専用(Chrome 123以降)です。他のブラウザの場合は、Chrome DevToolsのPerformanceパネルのトレースを使用してレンダリング処理を分析してください。
スクリプトの実行終了からフレームの終了までの差は純粋なレンダリングコストを表しており、これがプレゼンテーション遅延となります。このデータを観察してログに記録する方法は次のとおりです。
// LoAF APIを使用してプレゼンテーション遅延を測定する
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
const scriptEnd = Math.max(
...entry.scripts.map(s => s.startTime + s.duration)
);
const presentationDelay = (
entry.startTime + entry.duration
) - Math.max(scriptEnd, entry.renderStart);
console.log('Presentation delay breakdown:', {
totalDuration: entry.duration,
renderStart: entry.renderStart,
styleAndLayoutStart: entry.styleAndLayoutStart,
estimatedPresentationDelay: presentationDelay,
scriptCount: entry.scripts.length
});
}
}
});
observer.observe({
type: 'long-animation-frame',
buffered: true
}); CoreDashのようなRUMツールはLoAFデータを統合し、どのスクリプトやDOMの変更がレンダリング遅延を引き起こしているかを、完全なスクリプトの帰属情報とともに表示します。
他のINPフェーズを探索する
INPを制御下に置くためには、他の2つのフェーズにも対処してください。
- 入力遅延(Input Delay): イベントハンドラーの実行が開始されるまでの待ち時間を最小限に抑えます。入力遅延は通常最も短いフェーズですが、メインスレッドがビジー状態になるページの起動時に急増します。
- 処理時間(Processing Time): インタラクション中に実行されるイベントハンドラーのコードを最適化します。ほとんどのページにおいて、これは最適化の取り組みが最も成果を上げる部分です。
完全な診断ワークフローについては、INPの問題を見つけて修正する方法に関するガイドを参照してください。追加のレンダリング最適化戦略については、過大なDOMサイズの修正と未使用のCSSの削除に関するガイドを参照してください。全体の概要については、INPハブページに戻ってください。
何が本当に遅いのか、見つけ出します。
フィールドデータでCritical Rendering Pathをマッピング。Lighthouseレポートではなく、優先順位付きの修正リストをお渡しします。
監査を依頼する
