こんにちは。アウモ株式会社でインターンをしておりますイワミ(@B_Sardine)と申します。aumo では主に記事メディアを中心とした開発をしています。
今回は前回の記事の第二弾ということで、主にCLSについて述べていこうと思います。
目次
変更されたCLSアルゴリズム
おさらいですが、CLS(Cumulative Layout Shift )はページコンテンツの視覚的な安定性と言われており、ページ要素のずれの発生度合いを表す指標です。
この CLS ですが、実は今回の Google Core Update でアルゴリズムが変更されています。
web.dev によると…
Caution: Previously CLS measured the sum total of all individual layout shift scores that occurred during the entire lifespan of the page.
What is CLS? | web.dev
注意:以前は、CLSは、ページの存続期間全体で発生したすべての個々のレイアウトシフトスコアの合計を測定していました。
以前までは「ページ内で発生したレイアウトシフトを累積した合計」を計測していました。しかし今回から、Session Window という概念が追加されました。
これは、1つ以上の個別のレイアウトシフトが、各シフトの間に1秒未満、合計ウィンドウ期間で最大5秒の間隔ですばやく連続して発生した場合の一区切りを表したものです。この各 Session Window の中から、最も値の大きいものがこのページのスコアになります(下動画参照)。つまり今まではページ内全てのレイアウトシフトが累積されていたのが、最も集中して発生していた区間のみ見られるというアルゴリズムに変わったということです。
これにより、Google は CLS はほぼ全てのユーザにおいて良い変化が起きると主張していますが、aumo を始めとしたいくつかのサイトで悪い影響が発生しているようでした。
というのも、Google Search Console 上で5月末に突如として CLS に関するエラーが大量に発生したのでした。
しかし、先日の Page Speed Insights API で作成した計測基盤ではほぼ満点の値であり、原因がわからない日々が続いておりましたが、結果として以下の3点が原因ではないかと推測しました。
- そもそも CLS は Above the fold の以外の部分も関係する(フィールドデータが重要)
- 5-6月のLighthouse アップデート(v8.0.0) で CLS の重みが大きくなった
- Google 側の計測ミス
1. CLS はアンロードされるまで計測される
Page Speed Insights などで計測した “ラボデータ” は、Above the fold やファーストビューと呼ばれるような、ページ読み込み時に最初に表示されるスクロールせずに見ることができる範囲についてのみの計測を行います。
パフォーマンスチューニングにおいては、主にこの Above the fold をいかに早く快適に表示するかを求めるので、ツールを使用して計測したデータを元に改善していくのは得策です。
ですがここで注意しなければならないのは CLS です。先ほども述べたように、この CLS に関しては Above the fold だけでなくページ全体で計測されます。一般的にラボデータを計測できる Page Speed Insight などのツールは、CLS に関しても Above the fold の部分しか計測することができません。
以上より発生しうる現象は「ラボデータでは CLS のスコアが非常に良いのに、Google Search Console や CrUX などのフィールドデータが著しく低い」というものです。
つまり、 CLS に関してはラボデータ計測ツールで測るのではなく、 Chrome の開発者ツールの Performance などを利用してページ全体のレイアウトシフトを改善しなければならないということです。
さらに、これが完全に改善されたかどうかはフィールドデータでしか見ることができないので、実際に改善されているかどうかを確認するには1ヶ月ほどかかってしまいます…(CrUX のレポート更新は1ヶ月周期)
とはいえファーストビュー以外のCLS も効率的に計測したい
Lighthouse や Page Speed InsightsなどではファーストビューのCLS しか計測することができません。そこで利用されるのは、Chromeの開発者ツールの Performance ですが、重くて中々効率よく計測すことができません。そこで今回は「Layout Instability API」を使用しました。
使用法は簡単で、以下のコードを任意の <script>
タグに記述するだけです。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
}
}).observe({type: 'layout-shift', buffered: true});
Layout Shift を検出すると、コンソールに以下のようなものが表示されます。Value が大きさです。
Source > {arry_num} > node を確認すると、どこで発生しているかが分かります。この上にカーソルを合わせると、対応部分が色付きで表示されます。
これにより、効率よく原因箇所を探すことができます。(あくまで参考程度に考えておいた方がいいかもしれませんが)
2. Lighthouse がアップデートされた
公式よりアップデートの FAQ があり、その中で下表の重みの変化についてアナウンスされていました。
metric | v6 weigh | v8 weight | Δ |
First Contentful Paint (FCP) | 15 | 10 | -5 |
Speed Index (SI) | 15 | 10 | -5 |
Largest Contentful Paint (LCP) | 25 | 25 | 0 |
Time To Interactive (TTI) | 15 | 10 | -5 |
Total Blocking Time (TBT) | 25 | 30 | 5 |
Cumulative Layout Shift (CLS) | 5 | 15 | 10 |
特筆すべきところは CLS です。なんと 10 も重みが上がっています。これにより、 CLS の値自体は変わっていない(むしろ Session Window により改善された)としても、重みが上がっているために警告が発生したのではないかと考えられます。
実際に CrUX での CLS を見ると、直近では大きな変化が発生していないことがわかります。(元からあまり良くありませんが…)
3. Google の計測ミス
Core Update と同時期に、我々と同様に CLS が急激に悪化しているという情報をちらほら見ました。
上記の PWA Night Conference 2021 の講演の質疑応答の中でも、CLS の急増は Google の誤検出ではないかという意見がありました。
実際に aumo でも同時期に急激に CLS が高くなりましたが、その後0に落ち着きました。このことからも、この期間の CLS は誤検出されていた可能性が高いと考えられます。
行った施策
今回行った施策としては、以下の二点です。
- 広告領域を事前に確保しておく
- 遅延読み込み画像の領域を事前に確保しておく
1. 広告領域を事前に確保しておく
CLS の原因としてよく解説されているものです。
aumo はその収益の一つして、メディア内の広告などがあります。その中でもインフィード広告などと呼ばれる、コンテンツの間に差し込まれるタイプの広告があります。基本的にこれらは、その部分で対象のスクリプトを読み込み表示させるという方法で実装されています。
しかし、これらの広告が既存のコンテンツを押し除けるように入り込むことと、これが CLS としてカウントされてしまいます。そこで、あらかじめ広告が確保するであろう高さを事前に空領域として確保しておきます。これのより、この領域を埋めるように広告が展開されるため、CLS が検出されなくなります。
2. 遅延読み込み画像の領域を事前に確保しておく
aumo の記事内の画像は、LCP 対策としてファーストビューのもの以外は遅延読み込みを行っています。しかし、遅延読み込みは、スクロールして領域に到着した際、その範囲を広げるように画像を展開します。
左右の画像を見比べてもわかるように、画像が読み込まれる前(左)から、読み込まれた後(右)にかけて、画像の高さ分のレイアウトシフトが発生してしまっていることがわかります。そこで、広告と同様に画像の領域も事前に確保することにしました。
aumo の記事内で使用されている画像は、アスペクト比や大きさが異なります。なのでそれぞれの画像の大きさをデータベースで保存しています。今回はそのデータをもとに、事前領域を確保しました。
Slim
div style="margin: auto;" data-save-area-photo-id="#{photo.id}" data-photo-url="#{photo.data.large.url}" data-width="#{photo.width}" data-height="#{photo.height}"
~
{img情報}
~
TypeScript
private savePhotoArea() {
const els = document.querySelectorAll('[data-save-area-photo-id]');
els.forEach((value, index, list) =>{
let phto_width:string = list[index].dataset.width;
let phto_height:string = list[index].dataset.height;
let photo_path:string = list[index].dataset.photoUrl
let extend:string = photo_path.split("/").reverse()[0].split('.')[1];
if ($(window).width() > Number(phto_width)){
value.style.width=phto_width + "px"
value.style.height=phto_height + "px"
}else{
let custom_height:number = ($(window).width() - 32) * (Number(phto_height)/Number(phto_width));
value.style.height=String(custom_height) + "px"
}
});
}
DB の Photo に関する数値を TypeScript に送り、そこで計算を行います。これをもとに 確保領域の height を決定します。viewpoint よりも画像が小さい場合は、無理に引き伸ばさずそのままのサイズを使用しています。
これにより、遅延読み込み画像の領域も事前に確保することができ、 CLS の発生を抑えることができました。
結果
以上の施策により、大きくCLSが改善されました。(赤のPoor CLS が 62.0% 減少)
aumo 記事メディアという性質上画像を多く使用するため、遅延読み込み画像の事前領域確保施策がかなり効果を得られたのではないかと考えられます。
まとめ
今回は、 CLS のアップデートでの変更点や計測方法、行った対策などを述べていきました。 CLS は リアルユーザーモニタリングが重要なので、中々すぐに結果が見えず根気が必要ですが、UX においては非常に重要な項目です。粘り強く取り組む必要があると感じました。
次回は、LCP 対策として行った「CriticalCSS」 について述べていこうと思います。
それではまた次回。よろしくお願いします。