大規模で複雑なレイアウトとレイアウトのスラッシングを回避する

公開日: 2015 年 3 月 20 日、最終更新日: 2025 年 5 月 7 日

レイアウトでは、ブラウザが要素のジオメトリ情報(サイズとページ内の位置)を把握します。各要素には、使用された CSS、要素の内容、親要素に基づく明示的または暗黙的なサイズ情報があります。このプロセスは、Chrome(および Edge などの派生ブラウザ)と Safari ではレイアウトと呼ばれます。Firefox では「再フロー」と呼ばれますが、プロセスは実質的に同じです。

スタイルの計算と同様に、レイアウト費用の直近の懸念事項は次のとおりです。

  1. レイアウトが必要な要素の数。これは、ページの DOM サイズの副産物です。
  2. レイアウトの複雑さ。

概要

  • レイアウトはインタラクション レイテンシに直接影響します
  • 通常、レイアウトはドキュメント全体に適用されます。
  • DOM 要素の数はパフォーマンスに影響するため、可能な限りレイアウトのトリガーを回避する必要があります。
  • 強制同期レイアウトとレイアウト スラッシングを回避するには、スタイル値を読み取ってからスタイルを変更します。

レイアウトがインタラクション レイテンシに与える影響

ユーザーがページを操作する際、その操作はできる限り迅速に行われるようにする必要があります。インタラクションの完了に要する時間(ブラウザが次のフレームを表示してインタラクションの結果を表示するまで)をインタラクション レイテンシといいます。これは、Interaction to Next Paint 指標が測定するページ パフォーマンスの要素です。

ユーザー操作に応じてブラウザが次のフレームを表示するまでの時間は、操作の表示遅延と呼ばれます。インタラクションの目的は、何かが発生したことをユーザーに知らせるための視覚的なフィードバックを提供することです。視覚的な更新には、その目的を達成するために、ある程度のレイアウト作業が必要になる場合があります。

ウェブサイトの INP をできるだけ低く抑えるには、可能な限りレイアウトを避けることが重要です。レイアウトを完全に回避できない場合は、ブラウザが次のフレームをすばやく表示できるように、レイアウト作業を制限することが重要です。

レイアウトは可能な限り避ける

スタイルを変更すると、ブラウザは、変更のいずれかでレイアウトの計算が必要かどうか、そのレンダリング ツリーを更新する必要があるかどうかを確認します。widthheightlefttop などの「ジオメトリ プロパティ」を変更する場合は、すべてレイアウトが必要です。

.box {
  width: 20px;
  height: 20px;
}

/**
  * Changing width and height
  * triggers layout.
  */

.box--expanded {
  width: 200px;
  height: 350px;
}

レイアウトはほとんどの場合、ドキュメント全体に適用されます。要素が多数ある場合は、それらすべてに位置と寸法を割り当てるまでに時間がかかります。

レイアウトを回避できない場合は、Chrome DevTools を使用してレイアウトにかかる時間を調べ、レイアウトがボトルネックの原因かどうかを判断することが重要です。まず、DevTools を開き、[タイムライン] タブに移動して [記録] をクリックし、サイトを操作します。録画を停止すると、サイトのパフォーマンスの詳細が表示されます。

複数の紫色のレイアウト ブロックを示す DevTools。
Chrome DevTools の [Layout] に長い時間が表示される。

上の例のトレースを確認すると、各フレームのレイアウト内で 28 ミリ秒以上が費やされています。アニメーションでフレームを画面に表示するのに 16 ミリ秒しかかからない場合、これは非常に長すぎます。また、DevTools には、ツリーサイズ(この場合は 1,618 要素)と、レイアウトが必要なノード数(この場合は 5)も表示されます。

レイアウトは可能な限り避けるべきというのが一般的なアドバイスですが、レイアウトを避けられないこともあります。レイアウトを回避できない場合は、レイアウトのコストが DOM のサイズに関連していることに注意してください。2 つの関係は密接に関連していませんが、一般に DOM が大きいほどレイアウト費用が高くなります。

強制同期レイアウトを避ける

フレームを画面に送信する手順は次のとおりです。

レイアウトとして Flexbox を使用する。
レンダリングの手順

まず JavaScript が実行され、次にスタイル計算、次にレイアウトが行われます。ただし、JavaScript を使用してブラウザにレイアウトを強制的に早期に実行させることは可能です。これは強制同期レイアウト(または強制リフローの)と呼ばれます。

最初に留意すべき点は、JavaScript が実行されると、前のフレームの古いレイアウト値がすべて判明し、クエリに使用できることです。たとえば、要素(「box」と呼びます)の高さをフレームの開始時に出力する場合は、次のようなコードを記述します。

// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

高さをリクエストする前にボックスのスタイルを変更した場合は、問題が発生します。

function logBoxHeight () {
  box.classList.add('super-big');

  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

高さに関する質問に回答するには、ブラウザは(super-big クラスを追加したため)まずスタイルの変更を適用し、次にレイアウトを実行する必要があります。その後で、正しい高さが返されます。これは不要な作業であり、費用もかかる可能性があります。

そのため、スタイルの読み取りは常にバッチ処理し、最初に読み取りを行い(ブラウザが前のフレームのレイアウト値を使用できる場合)、次に書き込みを行う必要があります。

前の関数をより効率的にするには、次のようにします。

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

ほとんどの場合、スタイルを適用してから値をクエリする必要はありません。最後のフレームの値を使用すれば十分です。スタイルの計算とレイアウトをブラウザの希望よりも早く同期的に実行すると、ボトルネックになる可能性があります。通常は行わないことをおすすめします。

レイアウトのスラッシングを回避する

強制同期レイアウトをさらに悪化させる方法があります。それは、多くのレイアウトを短時間に連続して行うことです。次のコードをご覧ください。

function resizeAllParagraphsToMatchBlockWidth () {
  // Puts the browser into a read-write-read-write cycle.
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = `${box.offsetWidth}px`;
  }
}

このコードは、段落のグループをループし、各段落の幅を「box」という要素の幅に合わせて設定します。問題は、ループの各反復処理でスタイル値(box.offsetWidth)を読み取り、すぐにその値を使用して段落の幅(paragraphs[i].style.width)を更新することです。ループの次の反復処理で、ブラウザは offsetWidth が最後にリクエストされた(前の反復処理で)以降にスタイルが変更されていることを考慮し、スタイルの変更を適用してレイアウトを実行する必要があります。これはすべてのイテレーションで発生します。

このサンプルの修正方法は、値を再度読み取りしてから書き込みすることです。

// Read.
const width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth () {
  for (let i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = `${width}px`;
  }
}

強制同期レイアウトとスラッシングを特定する

DevTools には、強制同期レイアウト(「強制再描画」とも呼ばれます)のケースをすばやく特定できる強制再描画分析情報があります。

強制再描画を発生させている「w」という関数を特定する強制再描画分析情報を示す DevTools。
Chrome DevTools の強制再読み込みに関する分析情報。

強制同期レイアウトは、forcedStyleAndLayoutDuration プロパティを使用して Long Animation Frame API スクリプト アトリビューションを使用してフィールドで特定することもできます。