フォーマット未確定な動的生成画像を <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.php
は input
パラメーターで指定された画像ファイルを、 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&output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
<source srcset="image-convert.php?input=image.jpg&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.
source
に後続する同胞に[ 別のsource
要素 /srcset
属性を有するimg
要素 ]がある場合、source
は 次のいずれかは満たしていなければならない:
media
属性が指定されていて、その値は次を満たす :ASCII 小文字化する( 前後の ASCII 空白列を剥ぐ( 値 ) ) ∉ { 空文字列, "all
" }
type
属性が指定されている
triple-underscore.github.io
)
というわけで、<picture>
要素の子として複数の <source>
要素がある場合、最後の <soruce>
要素以外は media
属性ないし type
属性が必須となります。逆に言えば最後の <soruce>
要素は srcset
属性だけでも良いので、今回のケースではこうしてしまえば良いのです。
<!-- ✔ 良い -->
<picture>
<source srcset="image-convert.php?input=image.jpg&output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
<img src="image.jpg" alt=""/>
</picture>
もう一つのパターンとして、気にせず type
属性を書いてしまう方法もあります。
<!-- ✔ 良い -->
<picture>
<source type="image/avif" srcset="image-convert.php?input=image.jpg&output=avif"/><!-- AVIF 未対応環境では WebP が返る -->
<source type="image/webp" srcset="image-convert.php?input=image.jpg&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.
source の
type
属性は、ソース集合内の各 画像の型を与える — これにより,UA は、所与の型をサポートしない場合には, source を飛ばして 次以降の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
属性で指定したリソースに合わせた値を記述するというよりも、ブラウザがファイルタイプのサポート状況によってリソース選定をするための情報を提示するためのものと言えそうです。