[HTML] 今更だけど、ちゃんとレスポンシブな画像の設定方法を理解する。srcsetとsizesを使いこなそう。

世間のWebサイトをみてみると「横幅120pxのサムネイルに、1600pxの画像が使われている」ということがちょいちょいありまして、ソースコードを確認してみると

> ウインドウサイズが2000pxの場合の場合は800pxで表示して、スマホだと120pxで表示する。
> またRetina対応をしているから、解像度の2倍が必要で、800px@2xだから、大きい方にあわせてる

というパターンを結構みかけるので、あらためて`img`の属性である`srcset`と`sizes`についてまとめておきます。

# 大きい画像はファイルサイズが大きい
おそらくこの記事をご覧いただく方には「何を当たり前な」と思われると思うのですが、もう少しお付き合いください。

猫のオリジナル画像があります。ちなみに、この画像は横幅1024pxで、ファイルサイズは74.0 KBです。

![オリジナル画像](https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg)

これをサムネイルで横幅150pxで表示した場合は、ファイルサイズは4.2 KBになります。1/10以下です。

![サムネイル画像](https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=150)

この場合、画像1枚最適化すると__70 KB__近く省略できるようです。ちなみに、巷でよくいわれる高速化テクニック「minify」ですが、minify前で1万行を超えるjQuery 3.3.1 は、minify前が78.9 KB、minify後30.0 KBで、差が__48.9KB__です。1万行を超えるJSのminifyと、この画像のサムネイル化はほぼ同等のようです。
ちなみにWebサイトで、1ページに画像を複数を使うことよくあるので、画像数に比例しファイルサイズのダイエットは可能です。

# `srcset` で複数画像を用意すればいいんじゃないの？
`img` タグには、`srcset` 属性を使うことができます。簡単にいうと、`img` タグでレスポンシブに画像の横幅判定をするための属性です。

例えば以下のような`img`タグを用意したとしましょう。

```html
<img srcset="
  https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=150 150w, 
  https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=300 300w, 
  https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=500 500w, 
  https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=800 800w, 
  https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=1000 1000w
">
```

`srcset` は、画像URLとスクリーン幅（MediaQuery）のセットで利用します。ですので

```
https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=300 300w,
```

というのは、前半が`画像URL`、後半の `150w` がビューポート幅で、「ビューポート幅が300w以下の場合は、この画像URLを利用する」という意味です。複数組み合わせることによって、自動的にレスポンシブに画像を差し替えることができます。
なお、一致しなかった画像が読み込まれません。

## ビューポート幅に対応という意味。
「ビューポート幅」という言葉を見慣れないと思うのですが、簡単にいうと画面の横幅です。

```css
@media all and (min-width: 1024px)
```

で画面の横幅を設定しますよね。@1x（解像度1倍）の場合は、`1024px`と`1024w`は同じ意味を持ちます。

けれど、@2x（Retina / 解像度2倍）の場合、`2048px`は`1024w`と一致します。最近@4x（Retina / 解像度4倍）なる端末もでているのですが、この場合、__`4096px`は`1024w`と一致します__。え？？

ディスプレイの高解像化とそれに一致する画像の読み込みが進んだことで、150pxを@4xで表示しようとすると、600pxの画像が呼び出されるわけです。どういうこと。

## sizesでビューポート幅を設定
ここででてくるのが、 `img` タグの `sizes` 属性です。なお、`img` タグに width / height が設定されていないと使えない子ですので、お気をつけください（WordPressの場合は大体自動で挿入されるので大丈夫です）

sizesでは`vw`という単位を使うことで解像度の設定ができます。100vwが解像度100%を意味しますので

```html
sizes="50vw"
```

を設定すると、設定されている解像度が50%になります。

- @1x（解像度1倍）の場合は、`512px`と`1024w`
- @1x（解像度1倍）の場合は、`1024px`と`1024w`

が一致するようになります。ですので、例えば@2x、@3xでも無視して画像を用意したい時は以下のように用意します。（`resolution: 2dppx`が、解像度2倍を意味するMediaQueryです）

```html
sizes="(resolution: 2dppx) 50vw, (resolution: 3dppx) 34vw"
```

ですので、「@2xでは、解像度2倍はいらないけど1.2倍かな。@3xでは、1.4倍用意しよう」という時は以下のように設定すれば大丈夫です。

```html
sizes="(resolution: 2dppx) 60vw, (resolution: 3dppx) 47vw"
```

## ビューポート幅は画像幅じゃないので画像にあわせよう
まだあります。

そもそもですが、ビューポート幅は画像幅じゃないのです。「200pxの画像を表示させたいのに、ビューポート1024pxの時に1024wを表示させる」とかしたら、本末転倒なわけです。

どうしたらいいかというと、ビューポート幅に対応する画像の割合を算出すればいいわけです。

例えば、ビューポート幅1000px以下、解像度1倍の時に、200pxのサムネイルを表示させたい場合、画像の横幅はビューポート幅の20%となります。この場合の設定は以下のようになります。

```html
<img 
 srcset="https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=200 200w"
 sizes="(max-width: 1000px) and (resolution: 1dppx) 20vw"
>
```

解像度2倍時は300pxで表示したい時はこうですね。

```html
<img 
 srcset="https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=200 200w, 
         https://dp5figrfxl9dq.cloudfront.net/wp-content/uploads/2018/10/DSC_1217-20181001113727.jpg?d=300 300w"
 sizes="(max-width: 1000px) and (resolution: 1dppx) 20vw, (max-width: 1000px) and (resolution: 2dppx) 30vw"
>
```

# 簡単な設定方法
整理するために、何がややこしいかを考えると、以下の3点になります。

- srcsetで、ビューポイント幅に応じた画像を用意しないといけない
- 解像度2倍、3倍がある
- ブレイクポイント毎にウインドウ幅に対する画像の横幅を設定する必要ある

いろいろ試してみたのですが、私はできるだけ変数をへらすべく、 `srcset` を用意する時は、画像の横幅（px）とビューポイント幅は一致させています、これも変更しはじめると、きりないので・・・。

で、そうすると、解像度とウインドウ幅に対する計算式だけですので `sizes` を機械的に生成することができるようになります。具体的には、ブレイクポイントと解像度毎に用意するので、以下のようにつくっていくことができます。

```
(max-width: 1000px) and (resolution: 1dppx) 20vw, 
(max-width: 1000px) and (resolution: 2dppx) 12vw, // 20/2*1.2
(max-width: 1000px) and (resolution: 3dppx) 9vw,  // 20/3*1.2
(max-width: 1000px) and (resolution: 4dppx) 6vw   // 20/4*1.2
```

それぞれのビューポート幅毎に設定することで、ちゃんと意図通りのレスポンシブができますね！特に@3x, @4xはモバイルがほとんどですので、「モバイルだから表示速度が大切なのに、高解像度すぎて画像が表示されない。けど、解像度1倍よりはちょっちいい画像をだしたい」とお困りの時にご参考になりましたら幸いです。

## もう自動化させようよ
と思ったのですが、やってられないし、他人に計算してくださいはもっといえないので、ブレイクポイント毎にsizesを提案するジェネレーターを作成しました。

[「画像がなかなか表示されない」を改善。rabify CDN](https://www.rabify.me/)

![スクリーンショット 2018-10-05 12.12.38.png](https://qiita-image-store.s3.amazonaws.com/0/100460/f3bec9a1-3a7f-a514-babd-1bb9b4388aff.png)

画像をCDNで配信して、かつ末尾に ?d=300 などと引数をつけるだけでクラウドで自動的にリサイズできるrabify CDNとともにご利用いただけましたら幸いです。（rabify CDNを使わなくても、sizesの計算に使えるので、単独でもお使いください）

注意点として、ブレークポイントは「CSSで設定されているブレイクポイントではなく、レスポンシブのブレイクポイントの中で、画像サイズが最も大きい時」です。多くの場合、CSSで設定されているブレイクポイントの1px下になるんじゃないかな。

それでは、また！