画像最適化戦略 Picture 編

Intro

本サイトで使用している PNG/JPEG 画像を、対応デバイスと、 Device Pixel Ratio に対して最適なサイズで出し分けるために、 <picture> 要素を適用した。

画像最適化シリーズ第 2 回目のエントリである。

画像の出し分け

本サイトでは、それぞれの画像のサイズを、適切にリサイズしている。

しかし、例えば Device Pixel Ratio が大きい Retina 対応端末などには、 大きいサイズのファイルを提供しなければ、拡大表示による画像の荒れが発生してしまう。

そこで、同一画像でいくつかのファイルサイズを用意し、ブラウザの DPR などに応じて出し分けるのが望ましい。

最適な画像を決定する変数

サイズ/フォーマット的に最適なイメージは、主に端末とブラウザの組み合わせで変わる。

具体的には、現状以下の分岐を考慮する必要がある。

  • ViewPort Size はいくつか(端末サイズの分岐)
  • DPI はいくつか(端末 Retina 対応)

サイズ最適化

PNG/JPEG などのラスタ形式画像において、サイズとは基本的に縦横に敷き詰められた正方形の数を指す。

100x100 個の正方形でなる画像は、 100x100 で表示すれば基本的には問題ない。

しかし、 200x200 で表示すれば、同じ正方形を縦横二つ並べて大きくしないといけないため、鮮明さが落ちる。

ところがこれは、 CSS などで width=100 height=100 をきっちり指定すれば済むという問題ではない。

これは、 Device PixelCSS Pixel のサイズが必ずしも同じとは限らないからである。

Device/CSS Pixel Size

Retina ディスプレイは、通常のスクリーンよりも、スクリーン上に並ぶ正方形が小さいサイズになっている。

スクリーンが持つ 1x1 を描画するための四角は Device Pixel といい、これが小ほど 画面密度(Device Pixel Ratio) が高いと表現する。

もし Device Pixel Size が通常の半分の Retina ディスプレイで 100x100 の画像を素直に表示すると、同じスクリーンサイズの非 Retina で表示した時の 50x50 の見た目、つまり小さくなってしまう。

よって、 Retina 端末は、実際に自分が持っている Device Pixel と、 CSS で指定する CSS Pixel を分けることにした。

つまり、 CSS で 100x100 (CSS Pixel)で表示するように指定された画像は、勝手に 200x200 (Device Pixel)に拡大して表示することで、製作者が想定する 描画サイズ(見た目の大きさ) になるようにしている。

x2 と表される解像度は、 Device Pixel Size が CSS Pixel Size の二倍であることを意味する。

ところが、見た目の大きさのために拡大しているので、画像は当然荒くなる。

そのため、 Retina 対応するためには、あらかじめ 200x200 で作った画像も用意し、出し分ける必要がある。

今では x3 の端末もあるため、 300x300 の画像も必要になる。将来的にこれが増えていく可能性もゼロではないだろう。

逆に、大きいサイズの画像を小さく表示する分には、見た目上の問題は通常無い。

したがって、 300x300 を用意しておき、相手がだれであろうとそれを送って、 200x200 なり 100x100 なり都合の良いサイズで表示してもらえば、 見た目の問題 は解決する。

しかし、 100x100 のデータが必要な端末に 300x300 の画像を送るのは単純に無駄だ。単純計算で、本来必要なデータの 9 倍近いデータを送ることになる。

最適な画像を出し分けるのは、大事な一方、実は非常に複雑なのだ。

<picture> による対応

今回は、デバイスのサイズや解像度などに応じて、適切なサイズの画像を出し分けられるようにする設定を考える。

そこで <picture><srcset>sizes で DPR が大きい端末のための、大きいサイズの画像を指定することにした。

基本的に Width-Height は変更しない前提であれば、 DPI が x2, x3… と増える場合にサイズが x2, x3… となる画像を提供することになる。

この指定方法はいくつか考えられる。

Media Query の指定

Media Query を用いて DPR 値で分岐した場合以下のように指定できる。

<picture>
  <source type=image/png srcset=300x300.png media="min-device-pixel-ratio: 2.5">
  <source type=image/png srcset=200x200.png media="min-device-pixel-ratio: 1.5">
  <source type=image/png srcset=100x100.png>

  <img src=100x100.png alt="select with media query">
</picture>

sizes の指定

sizes を用いて、画像のサイズを明示した場合以下のように指定できる。

<picture>
  <source type=image/png sizes=100px
          srcset="100x100.png 100w,
                  200x200.png 200w,
                  300x300.png 300w">

  <img src=100x100.png alt="select with picture source">
</picture>

DEMO

これらの指定のデモは、以下に掲載した。

labs.jxck.io/picture

pros/cons

Media Query を用いた指定は、コンテンツ側が「この場合はこれ」とブラウザに指定していることになる。

ブラウザはそのクエリを評価し、その結果に忠実に画像を選択することになる。

sizes の指定は、コンテンツ側が「こういう選択肢がある」と提示し、ブラウザがそこから選択することになる。

srcset で画像の実際のサイズを w で指定し、それを表示したいサイズを sizes で指定している。

ブラウザは

「DPI x2 のディスプレイなので 100px で表示するのに最適なのは 200w の画像だ」

といった具合に画像を選択する。

両者の一番の違いは「ブラウザに考える余地があるかどうか」である。

もし、将来より大きい DPI や、今とは違う表示サイズや、表示形式から根本的に違うディスプレイが出て来た場合、後者はブラウザ自身がそれを考慮して適切な画像を選べるが、前者はクエリに手を入れて対応していく必要がある。

ブラウザに考える余地を与えるメリットは他にもある。

例えば、将来的にモバイル端末自身が

「ネットワーク環境が悪いため、 DPI は x3 だがあえて 100w の画像を取得しよう」

といったこともできる可能性がある。

ブラウザや置かれた状況によって取得される画像が変わるというのは、コンテンツの種類によっては問題になるかもしれない。

しかし、本サイトでは、同じ画像の中から必要十分なサイズの画像をリクエストして欲しいだけなので、問題はない。

本サイトは、こうした選択をブラウザに任せることとし、後者の指定方法を選択することにした。

結果

ただし、本サイトでは主に以下の画像を使う。

  • cacoo などで作成した画像
  • スクリーンショット

この中で、最初の画像については、基本的に SVG で出力する。

二番目の画像については、オリジナルよりも大きい画像を生成するのは難しい。

スクリーンショットを x3 のデバイスで取得できれば良いが、現状筆者はそれを持っていない。

したがって、大きな画像を用意して、小さく出せるものはあまりないため、この出し分けは大きな画像が取得できる場合のみにすることとした。