srcsetとsizesが理解できなかった人のために、日本一分かりやすく解説してみた


レスポンシブイメージとは

  • 画面幅や端末(パソコン、スマホなど)に応じて表示するイメージを切り替えること

なぜ重要なのか

  • スマホみたいな小さな画面で表示する時に5000x2500みたいな巨大画像を送りつけてしまうと、意味もなく通信時間がかかり、ユーザが可哀想
  • 逆にスマホ用の小さな画像をPCで開くと、画像がボケる
  • なので表示端末に応じて画像を切り替える必要がある

そのために何を理解する必要があるのか

  • HTML5でimgに追加されたsrcset属性, sizes属性
  • ブラウザがどうやって画像を選択しているのか
  • Retinaについて

凄まじく短いですが、今回の記事用にGitHubのレポジトリ作っときました

早速実装してみよう

サイズが異なるフラミンゴの画像を4つコチラから拝借しました。

普通にimg srcを指定して表示する

まずは普通にimgにsrcを設定します。

<img src="flamingo-fallback.jpg" />

で、表示してみます

flamingo-fallback.jpgが表示されました。ここまでは当たり前ですね。

srcsetを指定してみる

では次にsrcsetを使って、デバイスの幅に応じて異なる画像を表示してみましょう。こんな感じです。

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1000w,
    flamingo2x.jpg 2000w,
    flamingo3x.jpg 3000w"
    />

ここで注目して欲しいのが 1000w とか 2000w といった数字です。
説明を簡単にするために、今の段階では(w)=「ピクセル数」と考えてもらって構いません。

赤の1000wは「僕は1000ピクセルの幅を持った画像だよ」と示しています
青の2000wは「僕は2000ピクセルの幅を持った画像だよ」と示しています
緑の3000wは「僕は3000ピクセルの幅を持った画像だよ」と示しています

画像が、自分自身の幅(ピクセル数)をブラウザに伝えている状態です

ブラウザは伝えられた情報を元に、どの画像を表示するか選択します。
どう選択するのでしょうか?

ブラウザは自分の画面幅と画像幅を比較する

例えばブラウザの幅が1920pxだったとしましょう。
ブラウザはこんなことを考えます。

「1920pxの画面幅いっぱいに画像を表示したいなぁ。それぞれの画像のピクセル数は不足していないだろうか?」

先ほどのimgをもう一度振り返ってみましょう。

赤は1000ピクセルしかないので、1920ピクセルの画面に表示するにはピクセル数が不足していますね。
なのでこの画像は使えません。使えるけど、画像が荒くなってボケてしまいます。

「ピクセル数足りてるかな」と考えるプロセスは、こんな計算式で表現できます。

1920pxの画面幅に1000wの画像を表示する => 1000/1920 => 0.52
1920pxの画面幅に2000wの画像を表示する => 2000/1920 => 1.04
1920pxの画面幅に3000wの画像を表示する => 3000/1920 => 1.56

1を下回っている数字(例えば0.52)は「画像のピクセル数が不足している」ことを示します。

なのでブラウザは「うーん、赤はボケちゃうから使えそうにないな。比率が1以上(ピクセル数が十分)で、かつ一番小さい画像はどれかな」と考えて、青の2000wの画像を選択します。緑の3000wも比率は1を超えているので、使えるっちゃ使えます。が、同じく条件を満たした青の方が画像が小さいので、青を選びます。

こんな感じでブラウザは表示する画像を選択しています。
実際に先ほどのimgを表示してみると、ちゃんとflamingo2xが選択されていることがわかりますね。

もう少しイメージを膨らませるために、条件を変えて遊んでみましょう。

flamingo3xを表示させてみる

読み進める前に、ちょっとだけ考えてみてください。
srcsetをいじって、flamingo3xを表示するにはどうすればいいでしょうか?

.
.
.
.
.

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1000w,
    flamingo2x.jpg 1919w, <-- ここに注目!
    flamingo3x.jpg 3000w"
    />

そうです!
2xの画像が「ごめんごめん、僕は1919ピクセルだったわ」
と伝えれば、ブラウザは「じゃあ君は1920ピクセルの画面幅には足りないね」と考え直し、次に大きい3xを選んでくれます。

flamingo1xを表示させてみる

読み進める前に、ちょっとだけ考えてみてください。
srcsetをいじって、flamingo1xを表示するにはどうすればいいでしょうか?

.
.
.
.
.

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1921w, <-- ココに注目!
    flamingo2x.jpg 2000w,
    flamingo3x.jpg 3000w"
    />

そうですね!
1xの画像が「ごめんごめん、実は俺1921ピクセルだった」
と伝えれば、ブラウザは「お、1920ピクセルの画面に表示するのに十分じゃん」と考え直し、1xの画像を選んでくれます。

どうでしょうか、少しずつsrcsetのイメージが掴めてきたのではないでしょうか。

どうしていつも画面幅いっぱいに表示したがるの?

さて、ここで一つ疑問が浮かぶはずです。
「ブラウザの画面幅が1920ピクセルなのは分かった。でも、どうして画面幅いっぱいで表示できることを条件に探すの?別に全ての画像を画面幅いっぱいに表示する必要はないんじゃない?」と。

実はこの設定、あくまで デフォルトが画面幅いっぱい なだけで、いじれます。

そこで登場するのがsizesというattributeです。
こんな風に使います。

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1000w,
    flamingo2x.jpg 2000w,
    flamingo3x.jpg 3000w"
    sizes="10vw" <-- ココに注目!
    />

ここで初めて、新たな情報がブラウザに伝えられます。

「僕は画面幅いっぱいじゃなくて、画面幅の1/10ぐらいの大きさで表示できれば十分っすよ!」
ということをsizesは伝えています。

sizes="10vw"のvwは"viewport width"の略で、100vwを画面幅いっぱいとした時の比率を表します。

10vwなら10vw/100vwで画面幅の1/10、
50vwなら50vw/100vwで画面幅の半分

みたいな感じです。
なのでsizes=10vwは「僕は画面幅の1/10ぐらいの画像だよ」ということを伝えているわけです。

幅の1/10だけなら、もっと小さい画像で十分じゃないか!

すると先ほどのブラウザの計算が少し変わってきます。

「なぁんだ。1920ピクセルの画面いっぱいに表示する必要があると思ってたよ。1/10の、192ピクセルで十分だったんだね。じゃあ1x画像で十分じゃないか」

先ほどは1920ピクセルの幅が必要だったので2xの画像を選びましたが、
192ピクセルで済むなら、1xの画像で十分ですよね。

というわけで今度は1xの画像が選ばれるようになります。

もう少しいろんな数字で遊んでみましょうか。

flamingo3xを表示させてみる

さて、sizesだけを変えて無理やり3x画像を表示するには、どうすればいいでしょうか。 

.
.
.
.
.

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1000w,
    flamingo2x.jpg 2000w,
    flamingo3x.jpg 3000w"
    sizes="150vw" <-- ココに注目
    />

「うおおおおお!俺は画面幅の1.5倍の画像でなければいけないんやーーーーー!!!!でかい画像なんやーーーー!!!!」

ってことをブラウザに伝えてあげます。

ブラウザとしては「お、じゃあ1920ピクセルの1.5倍、2880ピクセルが必要なんだな。2x画像じゃあピクセル不足じゃないか!」

と考えて、flamingo3xを選ぶようになります。

ちなみにsizesを指定しなかった場合のデフォルトは100vwなので、「画面幅いっぱいにオイラを表示しておくれ」と伝えているようなイメージです。
だから序盤の説明では「果たして画面幅いっぱいに表示できるか?」をブラウザが考えていたわけですね。

どうでしょうか、sizesの使い方も少しわかってきたのではないでしょうか。

終わり!

以上がsrcsetとsizesの説明です。これで終わり!ハッピーハッピー!!

と思いきや、Retina

と思いきや、まだ一つ気をつけるポイントが残っています。Retinaです。
Retinaは「画像ピクセルが通常の2倍以上みっちり詰まった画面」と考えてください。

例えばこんなモバイル端末があったとしましょう
・10cmの幅
・幅には100ピクセル詰まってる

こいつをRetinaにすると、こうなります
・10cmの幅
・幅には200ピクセル詰まってる

要は「物理的に同じ幅だとしても、ピクセルが2倍詰まってる」のがRetinaだと覚えておきましょう。
厳密には違うのであとで補足しますが、説明の簡単のためにそう考えておいてください。

これがどう影響してくるか。先ほどのブラウザのピクセル計算にめちゃくちゃ影響してきます。

Retina様にはピクセルが足りない

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1000w,
    flamingo2x.jpg 2000w,
    flamingo3x.jpg 3000w"
    />

さてクイズです!
1920ピクセルの画面でこのimgを表示すると、どの画像が選ばれるでしょうか?

そう、flamingo2xですね。
1000wではピクセル数が足りないので、1920の次に大きい2000wの画像が選ばれるのでした。


では全く同じ画面幅1920でRetinaディスプレイだと、どの画像が選ばれるでしょうか?

flamingo3xが選ばれます。

Retinaディスプレイは2倍のピクセルが詰まっているので、1920ピクセルの画面幅に表示するためには、1920 x 2 = 3840ピクセル必要なんです。

なので実は3xですら不足しているんですが、これ以上大きい画像はないのでブラウザが「ペッ、シケてやがんな・・・まぁ今回はこれで許してやる!」って我慢してくれているわけです。

Retinaと非Retinaで比較してみる

例えば僕のMacBook ProはRetinaです。
なので開発者コンソールでこんなコマンドを入れて確認してみると、ピクセル比が2.0であることを確認できます

ちなみに外部モニタで同じコマンドを打つとこうなります

なので初めてRetina端末でsrcsetの動きを確認すると「あれ?非Retinaの時と挙動が違う」と感じる事があるかもしれません。

srcsetのwって何の単位?

序盤に「wのことは、ひとまずピクセルだと考えてください」と言いましたよね。
これは嘘で、正確には「画像の実際の寸法」です。
例えばさっきのflamingo1x.jpgをfinderで開いてみましょう。

ここに記載されている「1006」がwの正体です。

なので普通の端末なら1006pxあれば表現できるわけですが、pxが細かいRetinaだと2012px必要になるんですね。
Retinaを説明するまでここに言及したくなかったので、説明を先延ばしにしました。

ここを勘違いしている説明をよく見かけますが、srcsetのwは「僕はこれぐらいの画像サイズだよ!」と示していることを覚えておきましょう!

最後に1x,2x,3x記法について

以上がsrcsetとsizesの説明です。

と言いつつ、実はsrcsetには、こんな書き方もあります

    <img
    src="flamingo-fallback.jpg"
    srcset="
    flamingo1x.jpg 1x,
    flamingo2x.jpg 2x,
    flamingo3x.jpg 3x"
    />

えっ、1xって何!?wじゃないの!?

と混乱するかもしれませんが、ここまで読んだ方なら想像がついてしまうかもしれません!

これは単純に

  • ピクセル比が1ならflamingo1xを使う
  • ピクセル比が2ならflamingo2xを使う
  • ピクセル比が3ならflamingo3xを使う

って事です。
なので僕のMacBook Pro(Retina, ピクセル比2)ならflamingo2xを表示するし、
外部モニタ(ピクセル比1)ならflamingo1xを表示してくれるわけです。

ちなみにRetinaはあくまでAppleの商標であって、ピクセル比が1以外の画面を全部Retinaと呼ぶわけではありません。
なので Retinaじゃない=ピクセル比1 ではないのでご注意を!

最後に

以上でsrcsetとsizesの説明を終わります。長い間お付き合いいただき、ありがとうございました〜!

エンジニアとしてまだまだ未熟な身のため、もし勘違い、間違った情報があれば、遠慮なくご指摘ください。私自身にとっても勉強になりますので、厳しいご指摘も有り難く拝読します。

参考文献

flamingoの画像はこちらから拝借しました
MDN
How to Build Responsive Images with srcset