レイアウトは、要素の幾何学的情報(要素のサイズとページ上の位置)をブラウザによって管理する場所です。個々の要素は、使用された CSS、要素の内容、親要素に基づいて、明示的または暗黙的なサイジング情報を持ちます。このプロセスは、Chrome ではレイアウトと呼ばれます。
レイアウトは、要素の幾何学的情報(要素のサイズ、ページ上での位置)をブラウザで管理する場所です。各要素には、使用された CSS、要素の内容、親要素に基づいて、明示的または暗黙的なサイズ設定情報が含まれます。このプロセスは、Chrome(および Edge などの派生ブラウザ)と Safari では Layout と呼ばれます。Firefox ではリフローと呼ばれますが、実質上は同じプロセスです。
スタイルの計算と同様に、レイアウトのコストについて次の点を直接考慮する必要があります。
- レイアウトを必要とする要素の数。これは、ページの DOM サイズの副産物です。
- それらのレイアウトの複雑性
概要
- レイアウトはインタラクション レイテンシに直接影響します
- 通常、レイアウトはドキュメント全体に適用されます。
- DOM 要素の数はパフォーマンスに影響を与えます。可能な限り、レイアウトのトリガーを避けてください。
- 強制同期レイアウトとレイアウトのスラッシングを回避します。スタイル値を読み取ってから、スタイルを変更します。
レイアウトがインタラクション レイテンシに与える影響
ユーザーがページを操作する際、その操作はできる限り速やかに行われるようにする必要があります。インタラクションの完了に要する時間(ブラウザが次のフレームを表示してインタラクションの結果を表示するまで)をインタラクション レイテンシといいます。これは、Interaction to Next Paint 指標が測定するページ パフォーマンスの要素です。
ブラウザがユーザー操作に応答して次のフレームを表示するまでにかかる時間のことを、操作のプレゼンテーション遅延といいます。インタラクションの目的は、何かが発生したことをユーザーに知らせるための視覚的なフィードバックを提供することです。視覚的な更新には、その目的を達成するためにレイアウトの変更が伴う場合があります。
ウェブサイトの INP をできるだけ低く抑えるには、可能な限りレイアウトを避けることが重要です。レイアウトを完全に回避できない場合は、ブラウザが次のフレームをすばやく表示できるように、レイアウト作業を制限することが重要です。
可能な限りレイアウトを回避
ユーザーがスタイルを変更すると、ブラウザは、いずれかの変更によってレイアウトの計算が必要になるかどうか、そのためにレンダリング ツリーの更新が必要になるかどうかを確認します。幅、高さ、左、上部など、「幾何学的プロパティ」に対する変更はすべてレイアウトを必要とします。
.box {
width: 20px;
height: 20px;
}
/**
* Changing width and height
* triggers layout.
*/
.box--expanded {
width: 200px;
height: 350px;
}
ほとんどの場合、レイアウトのスコープはドキュメント全体になります。要素が多い場合、すべての要素の位置と寸法を把握するには長い時間がかかります。
レイアウトを避けることができない場合は、再び Chrome DevTools を使用して必要な時間を確認し、レイアウトが問題の原因であるかどうかを判定することが重要になります。まず、DevTools を開き、[Timeline] タブに移動し、記録を開始してサイトを操作します。記録を停止すると、サイトのパフォーマンスの詳細が表示されます。
上記の例のトレースを確認すると、各フレームでレイアウト内部で 28 ミリ秒超の時間が費やされています。アニメーションでフレームを画面に表示する時間が 16 ミリ秒であるのと比べて、これは長すぎます。また、DevTools では、ツリーのサイズ(この例では 1,618 要素)と、レイアウトで必要とされたノードの数(この例では 5)もわかります。
レイアウトは可能な限り避けるのが一般的ですが、レイアウトを避けられないこともあります。レイアウトを避けられない場合、レイアウトのコストは DOM のサイズに関係しています。2 つの関係は密接に関連していませんが、一般に DOM が大きいほどレイアウト コストが高くなります。
強制同期レイアウトを回避する
フレームを画面に配置する順序は次のとおりです。
最初に 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 してから、write します。
// Read.
const width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth () {
for (let i = 0; i < paragraphs.length; i++) {
// Now write.
paragraphs[i].style.width = `${width}px`;
}
}
安全性を保証する場合は、FastDOM の使用を検討してください。これは読み取りと書き込みを自動的にバッチ化するため、強制同期レイアウトとレイアウト転回の発生を防ぐことになります。