今からでも間に合う！ CLS対策 について

[虎の穴ラボ Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/tora-lab) 3日目の記事です。

JavaScriptで画像が挿入されるサンプルページを用いて、CLS対策の具体的な方法を紹介します。[CLS対策が必要な要素の調べ方](https://qiita.com/tora_oba/items/3c870b03d9bd863d91fd) の続きです。

Googleから2021年5月よりCWVをランキング要因に組み込むと発表がありました。今から取り組んでもまだ間に合うと思いますので、CLS対策が全然出来てないという方はぜひ参考にしてください。
## サンプルページの準備
サンプルで利用する以下のHTMLファイルをサーバーにアップロードします。

```sample.html
<h1 id="cls">クリスマスは同人誌が欲しい</h1>

<script>
 //初期表示されるh1要素の手前に画像を挿入します。
 var cls = document.getElementById('cls')
 cls.insertAdjacentHTML('afterbegin','<img src="緑髪園児">')
</script>
```

ちなみにサーバー上にHTMLファイルを置いた理由は、ローカル環境のHTMLファイルに対して、Light Houseによる監査を実行することが出来ないためです。


## レイアウトが動くページの確認
ページを開くと、画面の左上に「クリスマスは同人誌が欲しい」と一瞬だけ表示された後、緑髪園児画像が割り込むように現れました。
![レイアウトが動くページ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/caf87539-fac9-9513-a90a-4b811a85bd84.gif)
※ページの一部を抜粋してキャプチャしています。

なお、最初からimg要素がHTMLに記述されている場合でも、上記と同じ動きをします。

```sample.html
<img id="img" src="緑髪園児">
<h1 id="cls">クリスマスは同人誌が欲しい</h1>
```

## CLSの確認
それでは、スコアを計測してみます。サンプルページに対して、Light Houseの監査を実行したところ 0.015 でした。

この値は、低ければ低いほど良好です。本来、対策するまでもない程度の小さな値ですが、対策手順を紹介するため 0 を目指していきます。

![CLS対策前のスコア](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/64027d11-e74b-04c2-aa30-0222f8d22c92.png)

Light Houseの結果画面を少しだけスクロールすると、CLSに関する以下の項目が見つかります。
![CLS対策前のDiagnostics](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/e864bb3e-7e12-537e-65c0-0244926732a3.png)

ここで検出された項目に従って対処していくことで、CLSスコアを改善することが出来ます。大まかな流れは以下のとおりです。

1._Image elements do not have explicit width and height_ で検出されたimg要素を固定化
↓
2._Avoid Large Layout Shifts_ が表示されなくなった事を確認

それでは、検出された項目を１つずつ確認していきます。

### _1.Image elements do not have explicit width and height_
_(画像要素には明示的な幅と高さがありません)_

この項目で表示されているimg要素に対して、幅と高さを指定する必要があります。
しかし、外部CSSファイルに幅と高さを指定する方法では、この項目の対応として不適切です。
※詳細は後述します。
![幅と高さ警告](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/09f8a27c-6046-1b70-08d8-24efcb034a41.png)


### _2.Avoid Large Layout Shifts_
_(大きなレイアウトシフトを避ける)_

ここで検出された要素を動かないようにすれば、CLSスコアが改善されます。
要素を完全に動かなくできた場合は0となり、Light Houseでこの項目が検出されなくなります。
![動いている要素](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/b8db009c-47cd-6094-adf3-6fe0b0b1c873.png)

上記において、h1が動いている要素として検出されていますが、
h1要素を動かす原因となっているのは、JavaScriptで挿入されるimg要素の方です。

img要素の幅と高さは、読み込まれた画像の幅と高さに合わせて決定される為、
画像の読み込み完了と同時に要素が動いてしまいます。

そこで、img要素に対してはじめから幅と高さを指定し、
画像読込み前に画像領域を確保することで、要素の動きを防ぐ事ができます。

## CLS対策
CLS対策と謳ってますが、実際にやることは画像サイズの固定化です。
固定化の方法として大きく２パターンあります。
### 1.img要素に幅と高さを指定する(推奨)
```html
<img id="img" width="512" height="768" src="緑髪園児">
<!-- 数字の後にpxは不要です。 -->
```

上記のようにimg要素に幅と高さを指定した状態でページを開いてみると、
「クリスマスは同人誌が欲しい」という謎のメッセージ(h1要素)が現れなくなりました。

![tai.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/d84a9f8b-ed1b-8b2f-9a42-cbf019cdf8f8.gif)


その後、LightHouseで監査をかけるとCLSが0であることを確認出来ました。

![cls対策後のスコア](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/359a61c8-e054-1c54-b044-73eca765de5d.png)

先程検出されていた警告も消えており、この状態になればCLS対策として上出来です。
![cls対策後のDiagnostics](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/fdcdbcbc-52bb-462a-4759-e0f1ae0d599b.png)


ちなみに、img要素に幅と高さをインラインで記述しても同様の結果なります。

```html
<img id="img" style="width:512px; height:768px;" src="緑髪園児">
```


### 2.CSSファイルに記述する (非推奨)
外部のスタイルシートに記述することでも、CLS対策が可能です。

```style.css
img{
 width:512px;
 height:768px;
}
```

```sample.html
<link rel="stylesheet" href="./style.css">

<h1 id="cls">クリスマスは同人誌が欲しい</h1>

<script>
 var cls = document.getElementById('cls')
 cls.insertAdjacentHTML('afterbegin','<img src="緑髪園児">')
</script>
```

CLSスコアを確認してみると、0になっているので改善効果はありました。
![CLS対策２の結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/5acf53d9-1669-914f-068a-6a4b7809f0d9.png)
しかし、img要素に幅と高さを指定していない為、警告が残り続けてしまいます。
![幅と高さ指定の警告が残る](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738154/05a52bc6-4719-f436-4384-3d28e4d95536.png)

なお今回の場合、CSS→JavaScriptの順で読み込まれているのでスコアは改善されましたが、JavaScript→CSSの順で読み込まれた場合はレイアウトの動きを防げませんので、こちらの方法は非推奨です。

# まとめ
CLS対策は以下の理由から、img要素にwidthとheightを直接指定する方法を推奨します。

- Light Houseの幅と高さが指定されてないという警告が消える
- CSSの読み込みタイミングに関わらずimg要素を固定化できる