レイアウトの不安定性の修正

WebPageTest を使用してレイアウトの不安定性の問題を特定して修正する手順。

前回の記事では、WebPageTest でCumulative Layout Shift(CLS)を測定する方法について説明しました。CLS はすべてのレイアウト シフトの集計値であるため、この投稿では、ページ上の個々のレイアウト シフトを詳しく調べて、不安定性の原因を特定し、問題を実際に修正してみることにしました。

Layout Instability API を使用すると、ページ上のすべてのレイアウト移動イベントのリストを取得できます。

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

これにより、入力イベントの前に発生したレイアウト シフトの配列が生成されます。

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

この例では、210 ミリ秒で 0.01% という非常に小さな変化が 1 回発生しています。

変化の時間と重大度を知ることは、変化を引き起こした原因を絞り込むのに役立ちます。ラボ環境でさらにテストを行うため、WebPageTest に戻りましょう。

WebPageTest でのレイアウト シフトの測定

WebPageTest で CLS を測定するのと同様、個々のレイアウト シフトを測定するにはカスタム指標が必要です。幸い、Chrome 77 が安定したため、このプロセスは簡単になりました。Layout Instability API はデフォルトで有効になっているため、Chrome 77 内のどのウェブサイトでもその JS スニペットを実行してすぐに結果を取得できます。WebPageTest では、デフォルトの Chrome ブラウザを使用できるため、コマンドライン フラグや Canary の使用について心配する必要はありません。

そこで、WebPageTest のカスタム指標を生成するように、このスクリプトを変更します。

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

このスクリプトの Promise は、配列自体ではなく配列の JSON 表現に解決されます。これは、カスタム指標で生成できるのは文字列や数値などのプリミティブ データ型に限られるためです。

テストに使用するウェブサイトは ismyhostfastyet.com です。これは、ウェブホストの実際の読み込みパフォーマンスを比較するために作成したサイトです。

レイアウトの不安定性の原因を特定する

結果で、LayoutShifts カスタム指標の値を確認できます。

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

まとめると、3087 ms で 34.2% の単一のレイアウト シフトが発生します。原因を特定するには、WebPageTest のフィルムストリップ ビューを使用します。

フィルムストリップの 2 つのセルに、レイアウトのずれ前とずれ後のスクリーンショットが示されています。
フィルムストリップの 2 つのセルに、レイアウトの変更前と変更後のスクリーンショットが示されています。

フィルムストリップの 3 秒あたりまでスクロールすると、34% のレイアウト シフトの原因がカラフルな表であることがはっきりとわかります。ウェブサイトは JSON ファイルを非同期で取得し、テーブルにレンダリングします。テーブルは最初は空であるため、結果の読み込み時にテーブルが入力されるまで待機すると、シフトが発生します。

ウェブフォント ヘッダーが突然表示される。
ウェブフォント ヘッダーが突然表示される。

それだけではありません。ページが完全に表示されるまでに 4.3 秒ほどかかり、その時点で「Is my host fast yet?」ページの <h1> が突然表示されます。これは、サイトがウェブフォントを使用しており、レンダリングを最適化するための措置を講じていないことが原因です。この場合、レイアウトが実際にシフトしているように見えることはありませんが、タイトルを読むのにこれほど長く待たなければならないのは、ユーザー エクスペリエンスの面で好ましくありません。

レイアウトの不安定さを修正

非同期生成されたテーブルが原因でビューポートの 3 分の 1 がずれていることがわかりました。この問題を解決しましょう。JSON の結果が実際に読み込まれるまで、テーブルの内容はわかりませんが、なんらかのプレースホルダ データをテーブルに入力して、DOM がレンダリングされたときのレイアウト自体が比較的安定するようにすることができます。

プレースホルダ データを生成するコードは次のとおりです。

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

プレースホルダ データは、並べ替えの前にランダムに生成されます。ランダムな数だけ繰り返される「█」文字が含まれており、テキストの視覚的なプレースホルダと、3 つの主な値のランダムに生成された分布が含まれています。また、データがまだ完全に読み込まれていないことを明確にするために、テーブルのすべての色を彩度を下げて表示するスタイルも追加しました。

使用するプレースホルダの外観は、レイアウトの安定性には関係ありません。プレースホルダの目的は、コンテンツが配信されて、ページが壊れていないことをユーザーに保証することです。

JSON データの読み込み中は、プレースホルダは次のようになります。

データ表がプレースホルダ データでレンダリングされます。
データ表がプレースホルダ データでレンダリングされます。

ウェブフォントの問題に対処するのははるかに簡単です。このサイトでは Google Fonts を使用しているため、CSS リクエストで display=swap プロパティを渡すだけで済みます。以上です。Fonts API は、フォント宣言に font-display: swap スタイルを追加します。これにより、ブラウザは代替フォントでテキストをすぐにレンダリングできるようになります。修正を加えた対応するマークアップは次のとおりです。

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

最適化の検証

WebPageTest でページを再実行した後、前後の比較を生成して違いを可視化し、レイアウトの不安定性の新しい程度を測定できます。

レイアウトの最適化ありとなしでの両方のサイトの読み込みを並べて示す WebPageTest フィルムストリップ。
両方のサイトを横並びで読み込み、レイアウトを最適化した場合としない場合を表示している WebPageTest のフィルムストリップ。
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

カスタム指標によると、3,071 ミリ秒(以前とほぼ同じ時間)でレイアウト シフトが発生していますが、シフトの重大度は 0.005% と大幅に低下しています。これで問題ありません。

フィルムストリップからも、<h1> フォントがすぐにシステム フォントに戻り、ユーザーがすぐに読み取れることがわかります。

まとめ

複雑なウェブサイトでは、この例よりもレイアウト シフトがはるかに多くなると考えられますが、修正プロセスは同じです。レイアウトの不安定性に関する指標を WebPageTest に追加し、結果を視覚的の読み込みフィルムストリップと相互参照して原因を特定し、プレースホルダを使用して修正を実装して画面領域を確保します。

(もう 1 つ)実際のユーザーが経験するレイアウトの不安定さを測定する

最適化の前後でページで WebPageTest を実行し、指標の改善を確認することは良いことですが、本当に重要なのは、ユーザー エクスペリエンスが実際に改善されていることです。そもそも、サイトを改善しようとしているのはそのためではないでしょうか?

そのため、従来のウェブ パフォーマンス指標とともに、実際のユーザーのレイアウトの不安定なエクスペリエンスを測定できるようになれば、非常に有益です。これは最適化のフィードバック ループの重要な要素です。現場のデータがあれば、問題の場所や修正が効果的かどうかを把握できます。

独自のレイアウトの不安定性データを収集するだけでなく、Chrome UX レポートも確認してください。このレポートには、数百万ものウェブサイトでの実際のユーザー エクスペリエンスに基づく累積レイアウト シフトのデータが含まれています。これにより、自社(または競合他社)のパフォーマンスを確認したり、ウェブ全体のレイアウトの不安定な状態を調べたりできます。