【Core Web Vitals】CLSの要因と改善策をざっくりまとめ

出ましたね！！
[2021年5月から検索のランキング要因にCore Web Vitalsが入ってくる](https://www.suzukikenichi.com/blog/core-web-vitals-will-be-integrated-into-page-experience-signals-in-may-2020/)というアナウンス！
備忘録として対策メモ残しとく！

「Core Web Vitalsって何やねん！」って場合は、こちらをどうぞ↓
[『Core Web Vitalsについてざっくりまとめ』](https://qiita.com/rainymoment0616/items/d25915f991523c84d972)

## CLS(Cumulative Layout Shift)とは？
CLSとは累積レイアウトシフト変更。
簡単に言うと**「ユーザーが操作していないときに起こるレイアウトのずれ」をスコア化したもの**がCLS。
このCLSの数値が低いほど良い。
逆に大きいと、ユーザー体験的によろしくないんだねーと分かる。

下記みたいなことが、突然起こるレイアウト変更の例。。

- ページの中身読んでたら、突然後から読み込まれた画像が表示されて読んでたテキストの位置がずれてやむなくスクロールした
- ボタンやリンクを押そうとしたら、突然上から広告バナーが出てきて押してしまった

こういったことが起こった場合、レイアウトのずれが大きいほど、ユーザーが誤操作してしまったり、通常やらなくていい操作が増えてしまったり、ユーザー体験が低くなってしまう可能性が高くなる。

### レイアウトのずれをどうやってスコア化するのか

ちゃんと計算ができる！

```txt
レイアウトシフトスコア = 影響がある範囲の割合 * 実際にずれが起こった距離の割合
（画面全体を100%として、各割合を出す）
```

> ![](https://webdev.imgix.net/cls/layout-shift-3.png)
> 引用：https://web.dev/cls/#layout-shift-score

上記画像を参考に。
初回読み込み時は白いテキスト部分と緑のテキスト部分しかなかった（上図左）のに、白いテキスト部分にオレンジのボタンが出現して、緑のテキスト部分がズレている（上図右）。
このとき、影響がある範囲は、赤い点線枠の範囲（緑のテキスト部分）になるので、割合は0.5になる（画面全体の50%＝0.5）。
実際にずれが起こった距離は、青色の矢印の部分になるので、割合は0.14になる（画面全体の14%=0.14）。
以上から、レイアウトシフトスコアは、`0.5 * 0.14 = 0.07`となる。
このようなレイアウトのずれは色んな箇所で発生するので、それぞれスコアが算出される。
このスコアの合計値がCLSとなるわけですな。

## レイアウト変更が起きてしまう要因

- 寸法のない画像（width属性やheight属性が設定されていない）
- サイズのない広告、埋め込み、iframe
- 動的に挿入されたコンテンツ
- FOUT / FOITを引き起こすWebフォント
- DOM更新の前にネットワーク応答を待機するアクション（応答待ってからDOM更新されちゃうと、押し間違いが発生するため）

> 【参考】FOUTおよびFOITについて
> [『Font Display プロパティを用いた FOIT/FOUT 最適化』(blog.jxck.io)](https://blog.jxck.io/entries/2017-12-06/font-display.html)

## CLSを小さくするための対策

ざっくり簡単に言うと「レイアウト変更が起きないように、表示領域をしっかり確保しなさいよ」ってこと。

### 1. 画像や動画の要素にwidth属性・height属性を付与する
これは手っ取り早くできる方法。

```html
<img src="http://placehold.200x100/" width="200" height="100">
```

ただし、既存サイトなどで対応するのは大変なので、合間でちょこちょこやるでも良いかも。
JSでwidth属性とheight属性を付与すればいけるか？と思ったので、実務で運用しているサイトでHTMLに直接記述したものとJSで付与したものとで比べてみた。

**【下表】とあるページにwidth/height属性を付与したときのLighthouseの結果**

|  | 改善前 | 改善後<br>（HTMLに直接記述） | 改善後<br>（JSで属性付与） |
|:-:|:-:|:-:|:-:|
| パソコン | 2.48  | 0.07  | 0.64  |
| モバイル | 0.66  | 0.18  | 0.18  |

まぁ数値的には改善できたけど、JSの実行タイミングによっては意味なしな雰囲気なので、ちゃんと記述するほうが良さそう。

一時期、レスポンシブWebデザインのサイトが多くなって、widthやheightを指定するのがナンセンスみたいな感じになったときもあったけど、今は逆につけてあげたほうが良き。
widthやheightを指定してあげることで、「この画像のサイズ感（縦横比）はこのくらいよー」とブラウザに教えることになり、画像の表示領域を確保できる。
そのため、レイアウト変更が起きないわけですな。

> :warning: **注意**
> 画像サイズを可変にしたい場合は、CSSで `width: 100%`って設定するけど、それだけでなく`height: auto`もつけておくこと。
> それがないと、インラインで記述しているheightが効いてしまうので、画像の縦横比が崩れる。

### 2. 必要な表示領域を確保する

「アスペクト比ボックスを作る」ってのがあった。
表示領域を作るのに、固定値で設定しなくても、可変のボックスを作ることができる。
[『Aspect Ratio Boxes』(css-tricks.com)](https://css-tricks.com/aspect-ratio-boxes/)
CSSカスタムプロパティを利用してアスペクト比ボックスが作る方法が書いてあったけど、IE対応しなくちゃいけないサイトだと厳しい(ヽ´ω`)
（該当するコンポーネントを作るってのも手だけど、効率悪いので微妙。早くIEどうにかなってくれないかな。。。。）

### 3. ユーザーが操作する以外で、既存コンテンツの上にコンテンツを挿入しない

既に表示されてるコンテンツの上に、別のコンテンツが入ってきたりすると、既に表示されているコンテンツがずれてしまうので、それを防ぐための策。
もしも、コンテンツの上部や途中に、別のコンテンツを入れたい場合は、ちゃんとそれが表示される領域を予め確保しといてあげることが必要。

### 4. WebフォントはCDNを利用したり、`font-display:swap`を指定する

Webフォントはフォントファイルをサーバ上に置く方法とCDNを利用する方法とがあるけど、より表示スピードが早いCDNを利用するほうが、フォントが適用されるのが早いので、レイアウトのずれが起きにくくなる。
ただ、このフォントデータがダウンロードされて適用されるまでの間、どういう対応を取るかの指定ができるのが`font-display`。
このプロパティ値を`swap`にすることで、フォントデータがダウンロードされてる間は、代替フォントが適用されるので、レイアウトのずれが小さくなる可能性がある。

> 【参考】
> [『Webフォントを使う場合に font-display 記述子を使ってすぐにテキストを表示させる』 (ラボラジアン)](https://laboradian.com/show-text-immediately-using-font-display/)


### 5. レイアウト変更をトリガーするプロパティのアニメーションよりも変換アニメーションを使用する
なんのこっちゃな見出しだけど、単純に言うと、「transformプロパティを使いましょう」ってことかな。

最近、自動でアニメーションしたりするものが多くなってきたと思う。
よくあるのだと、スライダーとか？スクロールしたらコンテンツをフェードインさせたりとか？
追従バナーとかもそうかな？（スクロールされたらにゅっと出てくる）
そういうものを実装するときに、

- position
- top / right / bottom / left
- width(max-width) / height(max-height)
- margin / padding

などのプロパティをいじって実装するはず。
これらのプロパティは、レイアウト変更を引き起こすトリガーとなるので、あまりよろしくない。
（ユーザーが操作して動く分には、問題なし）
そのため、レイアウト変更を引き起こさずに要素をアニメーション化できるtransformプロパティを使用するといいよね！とされている。

いざ、実装をtransformに変更しようとしても、現状の自分では詰んだので、良き実装方法分かったら追記する予定。

> 【参考】
> - [『CSS Triggers』（CSSプロパティがどういうイベントのトリガーとなるかが分かる）](https://csstriggers.com/)
> - [『High Perfomance Animation』(HTML5 ROCKS)](https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/)