累積レイアウトシフト(CLS)の問題を修正する方法
公開: 2022-03-10累積レイアウトシフト(CLS)は、画像や広告などの新しいコンテンツがページの他の部分よりも遅く登場するときに、ページの不快な動きを測定しようとします。 ページのどの程度が予期せず移動しているか、およびその頻度に基づいてスコアを計算します。 これらのコンテンツのシフトは非常に煩わしく、読み始めた記事の場所を失ったり、さらに悪いことに、間違ったボタンをクリックしたりします。
この記事では、 CLSを削減するためのいくつかのフロントエンドパターンについて説明します。 以前の記事ですでに説明したように、CLSの測定についてはあまり話しません。 また、CLSの計算方法の仕組みについてもあまり話しません。Googleにはそれに関する優れたドキュメントがいくつかあります。JessPeckの「累積レイアウトシフトのほぼ完全なガイド」も、これについて深く掘り下げています。 ただし、いくつかのテクニックを理解するために必要な背景を少し説明します。
CLSが異なる理由
私の意見では、CLSはCore Web Vitalsの中で最も興味深いものです。これは、これまで実際に測定または最適化したことがないためです。 そのため、最適化を試みるには、多くの場合、新しい手法と考え方が必要になります。 これは、他の2つのコアWebバイタルとは非常に異なる獣です。
他の2つのコアWebVitalsを簡単に見ると、Largest Contentful Paint(LCP)はその名前が示すとおりに機能し、ページの読み込み速度を測定する以前の読み込みメトリックにさらにひねりを加えています。 はい、最も関連性の高いコンテンツの読み込み速度を確認するために、ページ読み込みのユーザーエクスペリエンスの定義方法を変更しましたが、基本的には、コンテンツができるだけ早く読み込まれるようにする古い手法を再利用しています。 LCPを最適化する方法は、ほとんどのWebページで比較的よく理解されている問題です。
First Input Delay(FID)は、相互作用の遅延を測定し、ほとんどのサイトで問題にならないようです。 これを最適化することは、通常、JavaScriptをクリーンアップ(または削減)することであり、通常はサイト固有です。 これらの2つのメトリックで問題を解決するのが簡単であるとは限りませんが、これらはかなりよく理解されている問題です。
CLSが異なる理由の1つは、ページの存続期間を通じて測定されることです。これは、名前の「累積的な」部分です。 他の2つのコアWebバイタルは、ロード後(LCPの場合)または最初のインタラクション(FIDの場合)にメインコンポーネントがページに見つかった後に停止します。 つまり、Lighthouseのような従来のラボベースのツールは、初期負荷CLSのみを計算するため、CLSを完全に反映していないことがよくあります。 実際には、ユーザーはページを下にスクロールし、より多くのコンテンツがドロップインしてシフトが増える可能性があります。
CLSは、ページの移動量と頻度に基づいて計算される、少し人工的な数値でもあります。 LCPとFIDはミリ秒単位で測定されますが、 CLSは複雑な計算によって出力される無次元数です。 このCoreWebVitalに合格するには、ページを0.1以下にする必要があります。 0.25を超えるものはすべて「不良」と見なされます。
ユーザーの操作によって引き起こされたシフトはカウントされません。 これは、ポインタイベントとスクロールは除外されますが、特定のユーザーインタラクションのセットから500ミリ秒以内と定義されます。 ボタンをクリックしたユーザーは、折りたたまれたセクションを展開するなどして、コンテンツが表示されることを期待している可能性があります。
CLSは、予期しないシフトを測定することを目的としています。 ページが最適に構築されている場合、スクロールによってコンテンツが移動することはありません。同様に、製品画像にカーソルを合わせてズームインバージョンを取得しても、他のコンテンツがジャンプすることはありません。 ただし、もちろん例外もあり、これらのサイトはこれにどのように対応するかを検討する必要があります。
CLSはまた、微調整とバグ修正によって継続的に進化しています。 シングルページアプリ(SPA)や無限スクロールページなど、CLSで不当にペナルティが科せられていると多くの人が感じていた、長寿命のページに少し休息を与えるという大きな変更が発表されました。 これまでのようにページ時間全体にわたってシフトを累積してCLSスコアを計算するのではなく、特定のタイムボックスウィンドウ内の最大のシフトセットに基づいてスコアが計算されます。
これは、0.05、0.06、および0.04のCLSの3つのチャンクがある場合、以前はこれが0.15として記録されていた(つまり、「適切な」制限である0.1を超えている)のに対し、現在は0.06としてスコア付けされることを意味します。 スコアがその時間枠内の別々のシフトで構成されている可能性があるという意味でまだ累積的です(つまり、0.06 CLSスコアが0.02の3つの別々のシフトによって引き起こされた場合)が、ページの全存続期間にわたって累積的ではなくなりました。
つまり、0.06シフトの原因を解決すると、CLSは次に大きいシフト(0.05)として報告されるため、ページの存続期間中のすべてのシフトを引き続き確認します。レポートのみを選択するだけです。 CLSスコアとして最大のもの。
CLSに関するいくつかの方法論の簡単な紹介で、いくつかのソリューションに移りましょう! これらの手法はすべて、基本的に、追加のコンテンツが読み込まれる前に適切な量のスペースを確保することを含みます。メディアまたはJavaScriptで挿入されたコンテンツであるかどうかにかかわらず、これを行うためにWeb開発者が利用できるいくつかの異なるオプションがあります。
画像とiFrameに幅と高さを設定する
これについては以前に書いたことがありますが、CLSを減らすためにできる最も簡単な方法の1つは、画像にwidth
とheight
の属性を設定することです。 それらがないと、画像はダウンロード後に後続のコンテンツをシフトさせてそれに道を譲ります。
これは、画像のマークアップを次の場所から変更するだけです。
<img src="hero_image.jpg" alt="...">
に:
<img src="hero_image.jpg" alt="..." width="400" height="400">
DevToolsを開き、要素にカーソルを合わせる(またはタップする)と、画像のサイズを確認できます。
固有のサイズ(画像ソースの実際のサイズ)を使用することをお勧めします。CSSを使用してこれらを変更すると、ブラウザーはこれらをレンダリングされたサイズに縮小します。
クイックヒント:私のように、幅と高さ、または高さと幅のどちらであるかを思い出せない場合は、X座標とY座標と考えてください。したがって、Xのように、幅が常に最初に指定されます。
レスポンシブ画像があり、CSSを使用して画像のサイズを変更する場合(たとえば、画面サイズの100%の最max-width
に制限する場合)、これらの属性を使用してheight
さを計算できます。 CSSのauto
:
img { max-width: 100%; height: auto; }
私の記事で取り上げたように最近までサポートされていませんでしたが、最近のすべてのブラウザーはこれをサポートしています。 これは、 <picture>
要素とsrcset
画像(フォールバックimg
要素でwidth
とheight
を設定)でも機能しますが、さまざまなアスペクト比の画像ではまだ機能していません。これは作業中です。それまでは、 width
とheight
を設定する必要があります。すべての値がデフォルトの0
よりも優れているためです0
これは、ネイティブの遅延読み込み画像でも機能します(ただし、Safariはデフォルトでネイティブの遅延読み込みをまだサポートしていません)。
新しいaspect-ratio
CSSプロパティ
レスポンシブ画像の高さを計算するための上記のwidth
とheight
の手法は、新しいCSS aspect-ratio
プロパティを使用して他の要素に一般化できます。これはChromiumベースのブラウザとFirefoxでサポートされていますが、SafariTechnologyPreviewでもサポートされています。うまくいけば、それはそれがすぐに安定したバージョンになることを意味します。
したがって、たとえば16:9の比率で埋め込みビデオに使用できます。
video { max-width: 100%; height: auto; aspect-ratio: 16 / 9; }
<video controls width="1600" height="900" poster="..."> <source src="/media/video.webm" type="video/webm"> <source src="/media/video.mp4" type="video/mp4"> Sorry, your browser doesn't support embedded videos. </video>
興味深いことに、 aspect-ratio
プロパティを定義しないと、ブラウザはレスポンシブビデオ要素の高さを無視し、デフォルトのアスペクト比2:1を使用するため、ここでのレイアウトシフトを回避するには上記が必要です。
将来的には、アスペクト比を使用して、要素の属性に基づいてaspect-ratio
を動的に設定することも可能になるはずaspect-ratio: attr(width) / attr(height);
しかし残念ながら、これはまだサポートされていません。
または、作成しているある種のカスタムコントロールの<div>
要素でaspect-ratio
を使用して応答性を高めることもできます。
#my-square-custom-control { max-width: 100%; height: auto; width: 500px; aspect-ratio: 1; }
<div></div>
aspect-ratio
をサポートしていないブラウザの場合は、古いパディングボトムハックを使用できますが、新しいaspect-ratio
のシンプルさと幅広いサポート(特に、これがSafariテクニカルプレビューから通常のSafariに移行すると)は次のようになります。その古い方法を正当化するのは難しい。
Chromeは、 CLSをGoogleにフィードバックする唯一のブラウザであり、コアWebバイタルの観点からCLSの問題を解決するaspect-ratio
意味をサポートしています。 ユーザーよりもメトリクスを優先するのは好きではありませんが、他のChromiumおよびFirefoxブラウザーにこれがあり、Safariがすぐに機能することを願っています。これはプログレッシブエンハンスメントであり、私たちがパディングボトムハックを残して、よりクリーンなコードを書くことができます。
min-height
を自由に使用する
レスポンシブサイズを必要とせず、代わりに固定の高さを必要とする要素については、 min-height
使用を検討してください。 これは、たとえば、固定の高さのヘッダーの場合であり、通常どおりメディアクエリを使用して、ブレークポイントごとに異なる見出しを付けることができます。
header { min-height: 50px; } @media (min-width: 600px) { header { min-height: 200px; } }
<header> ... </header>
もちろん、同じことが水平に配置された要素のmin-width
にも当てはまりますが、通常、CLSの問題を引き起こすのは高さです。
挿入されたコンテンツと高度なCSSセレクターのより高度な手法は、期待されるコンテンツがまだ挿入されていないときにターゲットを設定することです。 たとえば、次のコンテンツがある場合:
<div class="container"> <div class="main-content">...</div> </div>
そして、JavaScriptを介して追加のdiv
が挿入されます。
<div class="container"> <div class="additional-content">.../div> <div class="main-content">...</div> </div>
次に、次のスニペットを使用して、 main-content
のdivが最初にレンダリングされるときに追加のコンテンツ用のスペースを残すことができます。
.main-content:first-child { margin-top: 20px; }
このコードは、マージンがその要素の一部としてカウントされるため、実際にはmain-content
要素へのシフトを作成します。そのため、マージンが削除されるとシフトしているように見えます(実際には画面上で移動しませんが)。 ただし、少なくともその下のコンテンツはシフトされないため、CLSを減らす必要があります。
または、 ::before
疑似要素を使用してスペースを追加し、 main-content
要素のシフトを回避することもできます。
.main-content:first-child::before { content: ''; min-height: 20px; display: block; }
しかし、正直なところ、より良い解決策は、HTMLにdiv
を含め、その上でmin-height
を使用することです。
フォールバック要素を確認する
可能な限りJavaScriptがなくても、プログレッシブエンハンスメントを使用して基本的なWebサイトを提供するのが好きです。 残念ながら、これは最近、JavaScript以外のフォールバックバージョンがJavaScriptが開始されたときと異なっていたときに、私が維持している1つのサイトで私を捕らえました。
この問題は、ヘッダーの[目次]メニューボタンが原因でした。 JavaScriptが開始される前に、これは単純なリンクであり、目次ページに移動するボタンのようにスタイル設定されています。 JavaScriptが起動すると、動的メニューになり、そのページから移動したいページに直接移動できるようになります。
セマンティック要素を使用したため、フォールバックリンクにアンカー要素( <a href="#table-of-contents">
)を使用しましたが、JavaScript駆動の動的メニューでは<button>
に置き換えました。 これらは同じように見えるようにスタイル設定されていますが、フォールバックリンクはボタンよりも数ピクセル小さかったです!
これは非常に小さく、JavaScriptは通常非常に迅速に開始されたため、オフになっていることに気づきませんでした。 ただし、ChromeはCLSを計算するときにそれに気づき、これがヘッダーにあるため、ページ全体を数ピクセル下にシフトしました。 したがって、これはCLSスコアにかなりの影響を及ぼしました。これは、すべてのページを「改善の必要性」カテゴリに分類するのに十分です。
これは私の側のエラーであり、修正は単に2つの要素を同期させることでした(上記のようにヘッダーにmin-height
を設定することで修正することもできました)が、少し混乱しました。 このエラーを起こしたのは私だけではないと確信しているので、JavaScriptなしでページがどのようにレンダリングされるかに注意してください。 ユーザーがJavaScriptを無効にするとは思わないのですか? JSをダウンロードしている間、すべてのユーザーは非JSです。
Webフォントはレイアウトシフトを引き起こします
WebフォントはCLSのもう1つの一般的な原因です。これは、ブラウザが最初にフォールバックフォントに基づいて必要なスペースを計算し、次にWebフォントがダウンロードされたときにそれを再計算するためです。 通常、CLSは小さく、同じサイズのフォールバックフォントが使用されるため、Core Web Vitalsに失敗するほどの問題は発生しないことがよくありますが、それでもユーザーにとっては不快感を与える可能性があります。
残念ながら、Webフォントをプリロードしても、ここでは役に立ちません。フォールバックフォントの使用時間が短縮されますが(ロードパフォーマンスに適しています— LCP)、フェッチに時間がかかるため、フォールバックは引き続き使用されます。ほとんどの場合、ブラウザによって、CLSを回避しません。 つまり、次のページでWebフォントが必要であることがわかっている場合(ログインページを表示していて、次のページで特別なフォントが使用されていることがわかっている場合)、それらをプリフェッチできます。
フォントによるレイアウトのシフトを完全に回避するために、もちろんWebフォントをまったく使用できません。代わりにシステムフォントを使用するか、 font-display: optional
を使用して、最初のレンダリングに間に合うようにダウンロードしない場合は使用しないようにします。 しかし、正直なところ、どちらも非常に満足のいくものではありません。
もう1つのオプションは、セクションのサイズが適切であることを確認することです(たとえば、 min-height
を使用)。これにより、セクション内のテキストが少しずれても、その下のコンテンツが押し下げられないようにします。 たとえば、 <h1>
要素にmin-height
を設定すると、少し高いフォントが読み込まれた場合に記事全体が下にシフトするのを防ぐことができます。ただし、フォントが異なれば行数も異なりません。 これにより、シフトの影響が軽減されますが、多くのユースケース(一般的な段落など)では、最小の高さを一般化することは困難です。
この問題を解決するために私が最も興奮しているのは、CSSでフォールバックフォントをより簡単に調整できる新しいCSSフォント記述子です。
@font-face { font-family: 'Lato'; src: url('/static/fonts/Lato.woff2') format('woff2'); font-weight: 400; } @font-face { font-family: "Lato-fallback"; size-adjust: 97.38%; ascent-override: 99%; src: local("Arial"); } h1 { font-family: Lato, Lato-fallback, sans-serif; }
これらの前は、JavaScriptでFont Loading APIを使用して必要なフォールバックフォントを調整する必要がありましたが、このオプションは間もなくリリースされるため、最終的にはより簡単なソリューションが得られ、牽引力を得る可能性が高くなります。 この今後のイノベーションの詳細とそのリソースについては、このテーマに関する以前の記事を参照してください。
クライアント側のレンダリングされたページの初期テンプレート
多くのクライアント側でレンダリングされたページ、つまりシングルページアプリは、HTMLとCSSだけを使用して最初の基本ページをレンダリングし、JavaScriptをダウンロードして実行した後にテンプレートを「ハイドレイト」します。
新しいコンポーネントと機能がJavaScriptのアプリに追加されますが、最初にレンダリングされる最初のHTMLテンプレートには追加されないため、これらの初期テンプレートがJavaScriptバージョンと同期しなくなるのは簡単です。 これにより、これらのコンポーネントがJavaScriptによって挿入されたときにCLSが発生します。
したがって、すべての初期テンプレートを確認して、それらが依然として適切な初期プレースホルダーであることを確認してください。 また、最初のテンプレートが空の<div>
で構成されている場合は、上記の手法を使用して、シフトを回避するために適切なサイズになっていることを確認します。
さらに、アプリで注入される最初のdiv
は、最初のテンプレートが挿入される前に最初に0の高さでレンダリングされないように、 min-height
を持つ必要があります。
<div></div>
min-height
がほとんどのビューポートよりも大きい限り、たとえば、WebサイトのフッターのCLSを回避する必要があります。 CLSは、ビューポートにある場合にのみ測定されるため、ユーザーに影響を与えます。 デフォルトでは、空のdiv
の高さは0pxなので、アプリが読み込まれたときの実際の高さに近いmin-height
を指定します。
ユーザーインタラクションが500ミリ秒以内に完了するようにする
コンテンツをシフトさせるユーザーインタラクションはCLSスコアから除外されます。 これらは、相互作用後500ミリ秒に制限されています。 したがって、ボタンをクリックして、500ミリ秒を超える複雑な処理を実行してから、新しいコンテンツをレンダリングすると、CLSスコアが低下します。
次のスクリーンショットに示すように、 [パフォーマンス]タブを使用してページを記録し、シフトを見つけることで、シフトがChromeDevToolsで除外されたかどうかを確認できます。 DevToolsを開いて、非常に威圧的な(ただし、コツをつかんだら非常に便利です!)[パフォーマンス]タブに移動し、左上の[記録]ボタン(下の画像で丸で囲んだ部分)をクリックしてページを操作し、記録を1回停止します。完了。
別のSmashingMagazineの記事にコメントの一部を読み込んだページのフィルムストリップが表示されるので、丸で囲んだ部分で、コメントの読み込みと赤いフッターが画面外に移動していることを確認できます。 [パフォーマンス]タブのさらに下の[エクスペリエンス]行の下に、Chromeはシフトごとに赤みがかったピンクがかったボックスを配置します。それをクリックすると、下の[概要]タブに詳細が表示されます。
ここでは、 0.3359の大きなスコアが得られたことがわかります。これは、目標としている0.1のしきい値をはるかに超えていますが、最近の入力が[使用]に設定されているため、累積スコアにはこれが含まれていません。
インタラクションを確保すると、最初の入力遅延が測定しようとするものの境界内で500ミリ秒以内にコンテンツがシフトするだけですが、ユーザーが入力に影響があったことを確認できる場合があります(たとえば、読み込みスピナーが表示されます)。 500ミリ秒の制限が終了するまでページに追加されないため、CLSは不良です。
理想的には、インタラクション全体が500ミリ秒以内に終了しますが、処理の進行中に上記の手法を使用して必要なスペースを確保するためにいくつかのことを行うことができます。これにより、魔法の500ミリ秒以上かかる場合は、すでにシフトを処理しているので、ペナルティはありません。 これは、ネットワークからコンテンツをフェッチするときに特に役立ちます。コンテンツは可変であり、制御できない可能性があります。
注意すべき他の項目は、500ミリ秒以上かかるため、CLSに影響を与える可能性のあるアニメーションです。 これは少し制限があるように思えるかもしれませんが、CLSの目的は「楽しみ」を制限することではなく、ユーザーエクスペリエンスに合理的な期待を設定することであり、500ミリ秒以下かかると期待するのは非現実的ではないと思います。 ただし、同意できない場合、または考慮されていない可能性のあるユースケースがある場合は、Chromeチームがこれについてフィードバックを受け付けます。
同期JavaScript
これから説明する最後の手法は、よく知られているWebパフォーマンスのアドバイスに反するため、少し議論の余地がありますが、特定の状況ではこれが唯一の方法になる可能性があります。 基本的に、シフトを引き起こすことがわかっているコンテンツがある場合、シフトを回避するための1つの解決策は、落ち着くまでレンダリングしないことです。
以下のHTMLは、最初にdiv
を非表示にし、次にレンダリングをブロックするJavaScriptをロードしてdiv
にデータを入力し、それを再表示します。 JavaScriptはレンダリングをブロックしているため、これより下には何もレンダリングされず(再表示するための2番目style
ブロックを含む)、シフトは発生しません。
<style> .cls-inducing-div { display: none; } </style> <div class="cls-inducing-div"></div> <script> ... </script> <style> .cls-inducing-div { display: block; } </style>
この手法を使用してHTMLにCSSをインライン化することが重要であるため、順番に適用されます。 別の方法は、JavaScript自体でコンテンツを再表示することですが、上記の手法で気に入っているのは、JavaScriptが失敗したり、ブラウザーでオフにされたりしても、コンテンツを再表示できることです。
この手法は外部JavaScriptにも適用できますが、外部JavaScriptが要求されてダウンロードされるため、インラインscript
よりも遅延が発生します。 この遅延は、JavaScriptリソースをプリロードすることで最小限に抑えることができるため、パーサーがコードのそのセクションに到達すると、より迅速に利用できるようになります。
<head> ... <link rel="preload" href="cls-inducing-javascript.js" as="script"> ... </head> <body> ... <style> .cls-inducing-div { display: none; } </style> <div class="cls-inducing-div"></div> <script src="cls-inducing-javascript.js"></script> <style> .cls-inducing-div { display: block; } </style> ... </body>
さて、私が言うように、これは、JavaScript async, defer
、または新しいtype="module"
(デフォルトではdefer
されます)を使用してブロックを回避することをお勧めするため、一部のWebパフォーマンスの人々がうんざりすることになると確信していますレンダリングしますが、ここでは反対のことをしています! ただし、コンテンツを事前に決定できず、不快なシフトが発生する場合は、コンテンツを早期にレンダリングしても意味がありません。
この手法を使用して、ページの上部に読み込まれ、コンテンツを下にシフトするCookieバナーを作成しました。
これには、Cookieバナーを表示するかどうかを確認するために、Cookieを読み取る必要がありました。これはサーバー側で完了することはできましたが、返されたHTMLを動的に変更する機能のない静的サイトでした。
クッキーバナーは、CLSを回避するためにさまざまな方法で実装できます。 たとえば、コンテンツを下にシフトするのではなく、ページの下部に配置したり、コンテンツの上にオーバーレイしたりします。 コンテンツをページの上部に保持することを選択したため、シフトを回避するためにこの手法を使用する必要がありました。 サイトの所有者がさまざまな理由でページの上部に表示することを好む可能性のある他のさまざまなアラートやバナーがあります。
また、 JavaScriptがコンテンツを「メイン」列と「脇」列に移動する別のページでもこの手法を使用しました(ここでは説明しないため、HTMLサーバー側でこれを適切に構築することはできませんでした)。 JavaScriptがコンテンツを再配置し、それを表示するまで、コンテンツを再び非表示にして、これらのページのCLSスコアを引き下げるCLSの問題を回避しました。 また、JavaScriptが何らかの理由で実行されず、シフトされていないコンテンツが表示されている場合でも、コンテンツは自動的に非表示になります。
この手法を使用すると、レンダリングが遅延し、ブラウザの先読みプリローダーがブロックされる可能性があるため、他のメトリック(特に、LCPおよびFirst Contentful Paint)に影響を与える可能性がありますが、他のオプションが存在しない場合に考慮すべきもう1つのツールです。
結論
累積レイアウトシフトは、コンテンツのサイズが変更されたり、JavaScriptの実行が遅れて新しいコンテンツがページに挿入されたりすることで発生します。 この投稿では、これを回避するためのさまざまなヒントとコツについて説明しました。 Core Web Vitalsがこの苛立たしい問題にスポットライトを当ててくれてうれしいです—私たちWeb開発者(そして私は間違いなくこれに自分自身を含めています)がこの問題を無視してきたのは長すぎます。
私自身のウェブサイトをクリーンアップすることは、すべての訪問者にとってより良い体験につながりました。 CLSの問題も確認することをお勧めします。また、これらのヒントのいくつかが役立つことを願っています。 誰が知っているか、あなたはあなたのすべてのページのためにとらえどころのない0CLSスコアに到達することさえできます!
その他のリソース
- 画像の幅と高さの設定、コアWebバイタルの測定、CSSフォント記述子に関する私自身の記事を含む、SmashingMagazineのコアWebバイタルの記事。
- CLSのページを含むGoogleのCoreWebVitalsドキュメント。
- CLSに対する最近の変更の詳細と、この変更は、さまざまなGoogleのツールで更新を開始しました。
- Chromeの各バージョンでの変更の詳細を示すCLS変更ログ。
- JessPeckによる累積レイアウトシフトのほぼ完全なガイド。
- 累積レイアウトシフト:KarolinaSzczurによる視覚的不安定性の測定と回避。
- CLSの共有可能なデモンストレーションを生成するのに役立つレイアウトシフトGIFジェネレーター。