IE11も対象の案件。ブラウザの画面幅に応じてページのレイアウトが変わる際、一部の<img>要素の画像については縦横比が異なるものを表示させる必要がありました。そこで、サイズや縦横比が異なる複数の画像をあらかじめ用意しておき、画面幅に応じて自動で切り替わるようにしたい。最近のブラウザならHTMLの<picture>要素をサポートしているので、mediaクエリを付けた<source>要素を並べて書くだけで実現できますが、IE11は<picture>要素が未対応。もちろん、polyfill picturefill.jsの存在は知っています。が、今回はとりあえずmax-widthだけを基準にして画像を切り替えできれば十分だったので、自分でIE11用に簡易版のコードをJavaScriptで書いてみることにしました1。
※コンテントの画像 via Unsplash
##簡易版の仕様
簡易版jsが扱うことができるシンタックスは以下のとおりです。
- <source>要素には
srcset
属性とmedia
属性のみ指定します。 -
media
属性にはmax-width
1つのみ、px単位で指定します。 - <source>要素は
max-width
の値が小さい順に並べなければなりません。
<picture>
<source srcset="/path/to/img1" media="(max-width: 360px)">
<source srcset=/path/to/img2" media="(max-width: 420px)">
.....
<source srcset="/path/to/imgN" media="(max-width: 768px)">
<img src="/path/to/fallback/img" alt="">
</picture>
See the Pen [JS] picture element in IE11 by Kazuhiro Hashimoto (@kaz_hashimoto) on CodePen.
このCodePenはIE11以外でも動作を確認できるように2、HTMLネイティブの機能の代わりに簡易版jsを使って<picture>要素の画像を切り替え表示しています。実際の場面では、IE11以外には不要なコードなので、最初にブラウザのUA文字列によってIE11かどうかを判定し、IE11でなければ即returnさせます。
document.addEventListener('DOMContentLoaded', function() {
// 実際に使用する時は最初にUAでブラウザ判定を行い、
// IE以外のブラウザでは以降のコードが実行されないようにする。
// 例)
// const parser = new UAParser();
// const ua = parser.getResult();
// if (ua.browser.name != 'IE') {
// return;
// }
.....
実装メモ
処理の本体はIE11でも実行可能なようにコーディングする必要があるため、NodeListの各要素に対してcallback関数を呼ぶ箇所は、一旦Arrayを作ってからforEachを呼ぶ形式で書いてあります。
const targets = document.querySelectorAll('picture');
Array.prototype.forEach.call(targets, function(picture) {
...
<source>要素に設定した一連のmedia="(max-width:
は、区間が重ならないように
"(min-width: A px) and (max-width: B px)"
に変換したクエリでMediaQueryListオブジェクトを作成し、リスナーを設定します。
クエリとsrcset
の値のペアは、ハンドラ内からも参照するためMapオブジェクトで保持しておきます。ここで、Mapのキーに使うクエリ文字列は、matchMedia
の戻り値から取り出した方のmql.media
を使います。IE11の場合、それはmatchMedia(query)
に指定したquery
文字列と必ずしも一致しないためです。
const mql = window.matchMedia(query);
item.map.set(mql.media, source.getAttribute('srcset'));
const func = handler.bind(item)
mql.addListener(func);
func(mql);
変数item
は<picture>要素ごとに設定情報を保持するオブジェクトです。ハンドラー内でitem
をthis
キーワードから参照可能にするため、bind()
を呼んで生成した関数の方をリスナーに設定します。
function handler(mql) {
if (!mql.matches) {
return;
}
const item = this;
const src = item.map.get(mql.media);
if (!src) {
return;
}
item.img.src = src;
}
ハンドラーが呼ばれると、対象の<picture>要素に紐付けたitem
を取り出し、ステータスが変化したmediaクエリをキーにsrcset
の値を取り出し、<img>要素のsrc
に画像をセットします。