nostepments ブラウザのリフロー:何がトリガーとなり、Core Web Vitalsにどう影響するのか
リフローは要素の位置を再計算し、main threadをブロックします。何がトリガーになるのか、どのように検出し、どのように防ぐのかを学びましょう。
ブラウザのリフローとは?
リフローとは、ブラウザがページ上の要素の位置とサイズを再計算する処理のことです。DOMを変更したり、レイアウトに影響を与えるCSSプロパティを変更したりするたびに、ブラウザはすべての要素の配置を再計算する必要があります。この計算はmain threadをブロックします。計算が完了するまで、他の処理は一切行われません。
シンプルなページでの1回のリフローにかかる時間は数マイクロ秒です。しかし、ユーザーインタラクションの最中にこれを数百回も発生させると、main threadが数百ミリ秒もブロックされることになります。これだけで、Interaction to Next Paintで不合格になるには十分です。
最終レビュー:2026年3月 Arjen Karel
Table of Contents!
レンダリングパイプライン
リフローが高コストである理由を理解するには、ブラウザがページをレンダリングする仕組みを知る必要があります。すべての視覚的な変更は、最大5つのステージを経由します。
JavaScript → Style → Layout → Paint → Composite
JavaScript(またはCSS)が視覚的な変更をトリガーします。ブラウザはどのスタイルを適用するかを再計算します。次に、layout(リフロー)を実行して幾何学的形状(ジオメトリ)を計算します。その後、ピクセルをpaintします。最後に、レイヤーをcompositeします。
すべての変更がこれら5つのステージすべてを経由するわけではありません。ここが重要なポイントです。widthやheightを変更すると、パイプライン全体がトリガーされます。background-colorを変更すると、layoutを完全にスキップします(paintとcompositeのみを実行)。transformやopacityを変更すると、layoutとpaintの両方をスキップし、直接compositeに進みます。このパイプラインについての詳細は、web.devのレンダリングパフォーマンスに関するガイドで説明されています。
compositeのみの変更は低コストですが、layoutの変更はそうではありません。60fpsでは、ブラウザがすべての処理を行うための時間は1フレームあたり16.66msです。ブラウザのオーバーヘッドを差し引くと、コードの実行に使えるのは約10msです。その時間をリフローに費やしてしまうと、カクつきが発生します。
リフローを引き起こす要因
リフローを引き起こす原因は、現在のレイアウトを無効にする変更と、ブラウザに即座のレイアウト計算を強制するJavaScriptの読み取り操作の2つに大別されます。
layoutをトリガーするCSSプロパティ
以下のプロパティのいずれかを変更すると、ブラウザは強制的にリフローを実行します。
- サイズ:
width,height,padding,border,min-height,max-width - 位置:
top,right,bottom,left,margin - レイアウトモード:
display,float,position,flex,grid - テキスト:
font-size,font-family,font-weight,line-height,text-align,white-space - コンテンツ:
overflow,word-wrap
color、background-color、visibility、box-shadowなどのプロパティは再描画(repaint)をトリガーしますが、リフローは引き起こしません。transform、opacity、filterなどのプロパティは、どちらも引き起こしません。これらは、アニメーションやトランジションで優先的に使用すべきプロパティです。
同期的なレイアウトを強制するJavaScriptプロパティ
ここからが高コストになる部分です。特定のJavaScriptプロパティは、スクリプトの実行をブロックし、ブラウザに対して同期的に今すぐレイアウトを計算するように強制します。Paul Irish氏が網羅的なリストを公開しています(GitHubで5,000以上のスターを獲得)。代表的なものは以下の通りです。
offsetWidth,offsetHeight,offsetTop,offsetLeftclientWidth,clientHeight,clientTop,clientLeftscrollWidth,scrollHeight,scrollTop,scrollLeftgetBoundingClientRect()getComputedStyle()innerText(そうです、innerTextの読み取りでもレイアウトが強制されます)focus()scrollIntoView()
レイアウトプロパティを変更した後にこれらのいずれかを読み取ると、同期的なリフローが発生します。ブラウザは、まずレイアウトを計算しなければ値を返せないためです。Chrome DevToolsでは、30msを超える強制リフローはパフォーマンスのボトルネックとしてマークされます。
レイアウトスラッシング:避けるべきパターン
レイアウトスラッシングは、ループの中でレイアウトプロパティの読み取りと書き込みを交互に行うことで発生します。直前の書き込みによってレイアウトが無効化されるため、読み取りを行うたびにリフローが発生してしまいます。このパターンは、カルーセルのスクリプト、アコーディオンのプラグイン、要素の位置を測定するアナリティクスのコードなどで頻繁に見られます。
// BAD:ループの各反復でリフローが強制される
for (const el of elements) {
el.style.width = box.offsetWidth + 'px'; // 読み取り + 書き込み = 強制リフロー
}
反復ごとに offsetWidth を読み取り(レイアウトを強制)、style.width を書き込みます(レイアウトを無効化)。要素が100個ある場合、1回で済むはずのリフローが100回実行されることになります。
// GOOD:読み取りを一括で行い、その後に書き込みを一括で行う
const width = box.offsetWidth; // 1回だけの読み取り
for (const el of elements) {
el.style.width = width + 'px'; // 書き込みのみ、強制リフローは発生しない
}
1回の読み取り、1回のリフローで完了します。レイアウトスラッシングに関するweb.devのガイドでは、このパターンを詳細に解説しています。個々の要素のサイズを読み取る必要がある場合は、まずすべての読み取り操作をまとめて行い、その後にすべての書き込みを行ってください。
Chrome DevToolsでの強制リフローの検出
Performanceパネルを開き、トレースを記録します。強制リフローは、フレームチャート(flame chart)内に紫色の「Layout」ブロックとして表示されます。Chromeが強制的な同期レイアウトを検出すると、赤い三角の警告マークが表示されます。その上にカーソルを合わせると、JavaScriptのどの行がリフローをトリガーしたかが正確にわかります。
Consoleにも「Forced reflow while executing JavaScript took Xms」という警告が出力されます。30msを超える処理は問題です。私は、1つのスクロールイベントハンドラーが毎フレーム40msのレイアウト処理を引き起こしているサイトを見たことがあります。
Lighthouseもこれを検出します。Performanceカテゴリの「Avoid forced reflow」の診断項目を確認してください。
リフローがCore Web Vitalsに与える影響
Interaction to Next Paint (INP)
リフローは、INPに2つの形で直接影響を与えます。ユーザーがクリックしたときに強制リフローがすでに実行されている場合、main threadがブロックされているためinput delayが増加します。クリックハンドラー自体がレイアウト処理をトリガーする場合、processing timeが増加します。どちらの場合も、ブラウザは応答をpaintする前にレイアウトを完了する必要があるため、presentation delayも増加します。
INPの「良好」のしきい値は200msです。わずか30msの強制リフロー1回で、その予算の15%を消費してしまいます。イベントハンドラー内でのレイアウトスラッシングは、INPを容易に500ms以上に押し上げます。
CoreDashでモニタリングされているサイトでは、DOM of読み取りと書き込みを一括処理しているページは、レイアウトスラッシングのパターンがあるページと比較して、INPスコアが約18%優れています。
Largest Contentful Paint (LCP)
ページの読み込み中、リフローはmain threadの処理時間を奪い合います。フォントの読み込みはその典型例です。Webフォントが到着してfallbackテキストと置き換わる際、ブラウザはそのフォントを使用しているすべての要素をリフローします。テキストが多いページでは、このリフローによってLCPが100ms以上遅れる可能性があります。
明示的な width および height 属性を持たない画像も、同様の問題を引き起こします。2025 Web Almanacによると、モバイルページの62%に、サイズ指定のない画像が依然として少なくとも1つ含まれています。その画像が読み込まれると、ブラウザは実際のサイズに合わせてページをリフローします。
Cumulative Layout Shift (CLS)
リフロー自体が直接CLSを引き起こすわけではありません。CLSは、表示されている要素がユーザーの目に見える形で移動したときに発生します。しかし、後から読み込まれるコンテンツ(挿入された広告、サイズ未指定の画像、動的に追加された要素など)によるリフローは、ほとんどのレイアウトシフトの発生メカニズムとなっています。リフローのトリガーを修正すれば、レイアウトシフトは発生しなくなります。
レイアウトプロパティをアニメーション化するCSS transitionsも、もう1つの原因です。heightやmarginを変化させると、アニメーションのフレームごとにリフローが発生します。
モダンなCSSによるリフローの防止
compositeのみのアニメーション
width、height、top、leftの代わりに、transformやopacityをアニメーション化してください。これらはGPUのcompositor threadで実行され、レイアウトを完全にスキップします。要素を移動させるにはtransform: translateX()を使用し、視覚的にサイズを変更するにはtransform: scale()を使用します。2025 Web Almanacによると、モバイルページの40%が依然としてcompositeされないアニメーションを使用しています。これは、40%のページがすべてのフレームで不要なレイアウト処理を行っていることを意味します。
CSS containment
containプロパティは、要素の内部構造がページの他の部分から独立していることをブラウザに伝えます。containされた要素の内部で変更が発生した場合、ブラウザはドキュメント全体ではなく、そのサブツリーのみをリフローします。
article {
contain: content;
}
これは、DOMサイズが大きいページで特に有効です。リフロー時にブラウザがチェックしなければならない要素が多いほど、処理に時間がかかります。containmentはその影響範囲を限定します。
content-visibility
content-visibility: autoは、画面外にある要素のレイアウト、paint、スタイルの計算をスキップするようブラウザに指示します。Googleがこれを旅行ブログのデモでテストしたところ、レンダリング時間が232msから30msに短縮されました。約7倍の改善です。
.section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
contain-intrinsic-sizeは、スクロールバーの計算を正しく保つために、ブラウザにプレースホルダーとしての高さを提供します。このプロパティは2025年9月にBaseline Newly Availableとなり、すべての主要ブラウザで動作するようになりました。
実践的なチェックリスト
- DOMの読み取りは書き込みの前に行い、一括処理する。レイアウトプロパティの読み取りとスタイルの変更の書き込みを交互に行わないでください。
- 画像や埋め込み要素には明示的なサイズを設定する。これにより、リソースが読み込まれたときのリフローを防げます。
- アニメーションは
transformとopacityのみで行う。これらはレイアウトとpaintを完全にスキップします。 - 独立したセクションには
contain: contentを使用する。これにより、リフローの影響を変更があったサブツリーのみに限定できます。 - below-the-foldのセクションに
content-visibility: autoを追加する。これにより、画面外のコンテンツのレイアウトをスキップできます。 - floatよりもflexboxを優先する。同じ要素数の場合、flexboxによるレイアウトはfloatによるレイアウトよりも約4倍高速です。
- 負荷の高いDOM操作の間で、main threadにyieldする。これにより、ページの応答性を維持できます。
- DOMを変更するスクリプトは、初期レンダリングが完了するまで、スクリプトの実行を遅延(defer)させる。
これらの変更による実際の影響を、Real User Monitoringでモニタリングしてください。Lighthouseなどのラボツールはレイアウトコストを個別に示しますが、ユーザーが実際に改善を体験しているかどうかを示すのはfield dataです。