フォーマット未確定な動的生成画像を <source> 要素で指定する場合の type 属性

Web サイトで使用するラスター画像のフォーマットと言えば JPEG と GIF , PNG が主流でしたが、昨今は WebP や AVIF などの新しいフォーマットが普及し始めています。

これらの新フォーマットはまだすべてのブラウザが対応しているわけではないため、 <picture> 要素と <source> 要素を使ってブラウザが認識可能なリソースを選定できるようにすることがよく行われます。

<picture>
  <source type="image/avif" srcset="image.avif"/>
  <source type="image/webp" srcset="image.webp"/>
  <img src="image.jpg" alt=""/>
</picture>

このように、事前に静的ファイルを複数フォーマットで用意できる場合はとくに難しいことはありません。

それでは、サーバーサイドで画像が動的に生成されるケースで、かつレスポンスが何のタイプで返ってくるか分からない場合はどうすれば良いでしょう。

想定ケースとして、 image-convert.phpinput パラメーターで指定された画像ファイルを、 output パラメーターで指定した形式に変換するプログラムだとします。ただし、 Accept リクエストヘッダーを識別して未対応の場合は代替のフォーマットで返します。例えば image-convert.php?input=image.jpg&output=avif と指定すると、

  • AVIF に対応しているブラウザでは image/avif の画像を返す
  • AVIF 未対応の場合は image/webp の画像を返す
  • さらに WebP にも未対応の場合は image/jpeg の画像を返す

といった具合です。

この場合、同じ URL でもブラウザによってレスポンスのフォーマットが変わることから、いっそ type 属性を除去したくなるかもしれませんが、それはダメです。

<!-- ✘ 悪いマークアップ -->
<picture>
  <source srcset="image-convert.php?input=image.jpg&amp;output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
  <source srcset="image-convert.php?input=image.jpg&amp;output=webp"/><!-- WebP 未対応環境では JPEG が返る -->
  <img src="image.jpg" alt=""/>
</picture>

なぜなら、 <picture> 要素を親に持つ <source> 要素はこんな定義がされているからです。

When a source element has a following sibling source element or img element with a srcset attribute specified, it must have at least one of the following:

A media attribute specified with a value that, after stripping leading and trailing ASCII whitespace, is not the empty string and is not an ASCII case-insensitive match for the string "all".

A type attribute specified.

HTML Standard - 4.8.2 The source element(html.spec.whatwg.org)

source に後続する同胞に[ 別の source 要素 / srcset 属性を有する img 要素 ]がある場合、 source は 次のいずれかは満たしていなければならない:

media 属性が指定されていて、その値は次を満たす :ASCII 小文字化する( 前後の ASCII 空白列を剥ぐ( 値 ) ) ∉ { 空文字列, "all" }

type 属性が指定されている

HTML — Images 他 ( Embedded content )(日本語訳) - 4.8.2. source 要素(triple-underscore.github.io)

というわけで、 <picture> 要素の子として複数の <source> 要素がある場合、最後の <soruce> 要素以外は media 属性ないし type 属性が必須となります。逆に言えば最後の <soruce> 要素は srcset 属性だけでも良いので、今回のケースではこうしてしまえば良いのです。

<!-- ✔ 良い -->
<picture>
  <source srcset="image-convert.php?input=image.jpg&amp;output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
  <img src="image.jpg" alt=""/>
</picture>

もう一つのパターンとして、気にせず type 属性を書いてしまう方法もあります。

<!-- ✔ 良い -->
<picture>
  <source type="image/avif" srcset="image-convert.php?input=image.jpg&amp;output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
  <source type="image/webp" srcset="image-convert.php?input=image.jpg&amp;output=webp"/><!-- WebP 未対応環境では JPEG が返る -->
  <img src="image.jpg" alt=""/>
</picture>

環境によっては type 属性値と異なるフォーマットが返る状況は違和感を覚えるでしょうか? 確かにちょっと気持ち悪いですが、これは問題ありません。

<picture> 要素を親に持つ場合の <source> 要素の type 属性の説明はこうなっています。

The type attribute gives the type of the images in the source set, to allow the user agent to skip to the next source element if it does not support the given type.

HTML Standard - 4.8.2 The source element(html.spec.whatwg.org)

source の type 属性は、ソース集合内の各 画像の型を与える — これにより,UA は、所与の型をサポートしない場合には, source を飛ばして 次以降の source 要素を選定できるようになる。

HTML — Images 他 ( Embedded content )(日本語訳) - 4.8.2. source 要素(triple-underscore.github.io)

つまり、ブラウザは srcset 属性で指定されたリソースにアクセスを試みるまでもなく(fetch をせずとも)、 type 属性を見てサポートの有無を判断できるというわけです。

実際にブラウザがこの仕様に従っているか、わざと変わった HTML を用意してテストしてみました。

<!-- ✔ 一見おかしいが、仕様的に間違ってはいない -->
<picture>
  <source type="image/avif" srcset="image.webp"/><!-- type 属性では AVIF を指定しているが、実際の画像は WebP -->
  <source type="image/webp" srcset="image.avif"/><!-- type 属性では WebP を指定しているが、実際の画像は AVIF -->
  <img src="image.jpg" alt=""/>
</picture>

AVIF に対応している Firefox 94 や Chrome 95 では最初の image.webp が表示されます。一方、 WebP に対応しているが AVIF は未対応な iOS Safari 15 と Edge 95 では image.webp はスルーされて image.avif へのリクエストが行われます。 WebP を期待してリクエストを行ったのに実際には未対応の AVIF が返ってくるため、画面に画像は表示されませんが、これは正しい挙動です。

このため、 <picture> 要素の子の <source> 要素の type 属性は、 srcset 属性で指定したリソースに合わせた値を記述するというよりも、ブラウザがファイルタイプのサポート状況によってリソース選定をするための情報を提示するためのものと言えそうです。