yasuda

レスポンシブイメージのポリフィル「Picturefill.js」でブレイクポイントによって画像を切り替える

スマホでは縦長の画像を表示したいけれど、PCでは横長の画像を表示したいということがよくあります。
そんな時にはJavaScriptで横幅を判定して切り替えるという方法がよくとられると思います。例えば、以下のような方法ですね。

jQueryでウィンドウサイズによって画像を切り替える

最近では、pictureタグなどを使ったいわゆるレスポンシブイメージを使える環境が増えてきてきているのをご存知でしょうか。
レスポンシブイメージとは、「画像を読み込む前に、ブラウザ側で横幅や解像度などを考慮して、指定された、もしくは最適な画像ファイルだけを読み込んでくれるHTMLのネイティブな機能」です。対応しているブラウザであればCSSもJavaScriptも使わずに使用できます。

レスポンシブイメージで使用するpictureタグとsrcsetタグのブラウザの対応状況をCan I useで確認すると以下のような状況でした。

  • Chrome、Firefox、Safariは問題なし
  • IEとAndroidの標準ブラウザは全滅
  • srcsetタグはEdge 16から対応
  • pictureタグはiOS Safari 9.3から対応

案件で導入するにはまだ早いと感じてしまいますが、レスポンシブイメージを今からでも使えるようにしてくれる「Picturefill.js」というポリフィルがあるので紹介します。

Picturefill.jsのダウンロードと読み込み

下記リンクからダウンロードできます。記事公開時の安定版(Stable)は3.0.3となっています(GitHubと公式ドキュメントからのダウンロードは3.0.2になっていました)。

picturefill.js

CDNもCloudflareから提供されています(CDNは3.0.3になっています)。

<script src="https://cdnjs.cloudflare.com/ajax/libs/picturefill/3.0.3/picturefill.js"></script>

npmからインストールすることもできます。

npm install picturefill

基本的にはpicturefill.jsを読み込むだけで使えます。

<script src="picturefill.js"></script>

レスポンシブイメージ(アートディレクション)の指定方法

Picturefill.jsの詳しい使い方は公式サイトで紹介されています。
今回はレスポンシブイメージでアートディレクションを指定する方法を紹介します。

簡単なデモを用意しました。後述するマークアップ方法やフォールバックなどをテストしてみてください。

See the Pen Picturefill.js test by yasuda (@tam_yasuda) on CodePen.

以下のように指定をします。

<picture>
  <source media="(min-width: 1080px)" srcset="/assets/img/img-pc.jpg"/>
  <source media="(min-width: 768px)" srcset="/assets/img/img-tablet.jpg"/>
  <img src="/assets/img/img-sp.jpg" alt="alt属性値を指定します。"/>
</picture>
  • pictureタグでsourceタグとimgタグをラップします。
  • sourceタグの条件(media属性、メディアクエリ)を上から確認していき、条件に一致したものが適応されます。いずれの条件も一致しない場合はimgタグのsrc属性値が表示されます。
  • media属性値をmin-widthで指定する場合は幅の大きい条件から指定する必要があります。
  • ページ読み込み時だけでなく、リサイズして違う条件に合致した場合にも画像が切り替わります。

Picturefill.jsの注意点

Picturefill.jsの使用にはいくつかの注意点があります。
未対応ブラウザや特定のブラウザに対して、マークアップの変更やフォールバックが必要になることがあります。

src属性で指定すると画像を重複して読み込む場合がある

Trying to use the src attribute in a browser that doesn't support picture natively can result in a double download. To avoid this, don't use the src attribute on the img tag:

pictureタグにネイティブで対応していないブラウザでimgタグにsrc属性を使うと、画像を重複して読み込んでしまう可能性があるようです。src属性ではなく、srcset属性で指定するようにすると解消できます。

<picture>
  <source media="(min-width: 1080px)" srcset="/assets/img/img-pc.jpg"/>
  <source media="(min-width: 768px)" srcset="/assets/img/img-tablet.jpg"/>
  <img srcset="/assets/img/img-sp.jpg" alt="alt属性値を指定します。"/>
</picture>

試してみると、レスポンシブイメージに未対応のIE11やSafari 7(iOS7)ではsourceタグの条件(メディアクエリ)が合致した場合に、そのsourceタグとimgタグの2つの画像が読み込まれました(2つ以上読み込まれることはありませんでした)。
アートディレクションでは、スマホ・タブレット・PCで切り替えることが多いと思うので、スマホでの読み込みの重複はあまり起こらないと思います。問題はIEのパフォーマンスになりそうです。

src属性を記述しないと、以下のようにバリデーターでエラーになってしまうことにも注意してください。案件のブラウザ対応状況や方針などによって判断するといいと思います。

Error: Element img is missing required attribute src.

IE9に対応する場合はvideoタグでラップする

While most versions of IE (even older ones!) are supported well, IE9 has a little conflict to work around. To support IE9, you will need to wrap a video element wrapper around the source elements in your picture tag. You can do this using conditional comments, like so:

IE9に対応するには、pictureタグ内のsourceタグをvideoタグでラップする必要があります。コンディショナルコメントでIE9のときにだけラップするようにします。

<picture>
  <!--[if IE 9]><video style="display: none;"><![endif]-->
  <source media="(min-width: 1080px)" srcset="/assets/img/img-pc.jpg"/>
  <source media="(min-width: 768px)" srcset="/assets/img/img-tablet.jpg"/>
  <!--[if IE 9]></video><![endif]-->
  <img src="/assets/img/img-sp.jpg" alt="alt属性値を指定します。"/>
</picture>

Safariで画像が重複して読み込まれてしまう

Sometimes when using picturefill in Safari, the wrong image may briefly load before the correct source had been chosen. We call this the Flash of Wrong Image™. The only way to prevent this is by removing the src and srcset on the fallback image. It's important to note, however, that this may cause the image from displaying on old android devices (2.3).

Safariでは、適切な画像を読み込む前に、間違った画像が読み込まれることがあるようです。img要素のsrc属性とsrcset属性を空にすると防げるようです。

再現はできませんでしたが、Safariの未対応のバージョンで発生するのであれば、割合自体は少ないので、大きな問題はないかなと思います。

Pugのmixinで管理する

レスポンシブイメージでアートディレクションを指定する場合は、メディアクエリをHTMLに直接記述する必要がありますし、古いブラウザに対応するためにPicturefill.js独自のフォールバックを指定する必要もあります。
修正がしやすいように、Pugを使ったmixinをよく使っているので、その方法も紹介します(gulp-pugの3.3.0で動作を確認しています)。

ここでは、picturesourceというmixinを定義して、組み合わせて使います。

//- CSSのブレイクポイントに合わせて変数を設定してください。
- var xl = "(min-width: 1440px)"
- var lg = "(min-width: 1080px)"
- var md = "(min-width: 768px)"

//- <source>タグを記述するためのmixinです。
    +picture内にネストをして記述します。
    media - [variable] ブレイクポイントを管理している変数を指定します。
    source - [string]  画像へのパスを指定します。
    Pug: +source(md, 'https://placehold.jp/600x600.png')
    HTML: <source media="(min-width: 768px)" srcset="https://placehold.jp/600x600.png">
mixin source(media, source)
  source(media=media srcset=source)

//- <picture>でアートディレクションを使うためのmixinで、picturefill.jsに依存しています。
    IE9に対応しない場合はIE9用のコンディショナルコメントを削除してください。
    引数は<img>に対する指定になります。
    ブレイクポイントにmin-widthを指定しているので、+sourceは大きなサイズから記述する必要があります。
    fallback - [string] 画像へのパスを指定します。
    alt - [string]  alt属性値を指定します。
    Pug: +picture("https://placehold.jp/300x300.png", "alt属性値を指定します。").responsiveImage
           +source(lg, "https://placehold.jp/1000x1000.png")
           +source(md, "https://placehold.jp/600x600.png")
    HTML: <picture class="responsiveImage">
             <!--[if IE 9]><video style="display: none;"><![endif]-->
             <source media="(min-width: 1080px)" srcset="https://placehold.jp/1000x1000.png">
             <source media="(min-width: 768px)" srcset="https://placehold.jp/600x600.png">
             <!--[if IE 9]></video><![endif]-->
             <img srcset="https://placehold.jp/300x300.png" alt="alt属性値を指定します。">
mixin picture(fallback, alt)
  picture&attributes(attributes)
    <!--[if IE 9]><video style="display: none;"><![endif]-->
    if block
      block
    <!--[if IE 9]></video><![endif]-->
    img(src=fallback alt=alt)

以下のようにmixinを呼び出します。pictureタグへのクラス指定も可能です。

+picture("/assets/img/img-sp.jpg", "alt属性値を指定します。").responsiveImage
  +source(lg, "/assets/img/img-pc.jpg")
  +source(md, "/assets/img/img-tablet.jpg")

以下のように出力されます。出力内容は適宜変更してください。

<picture class="responsiveImage">
  <!--[if IE 9]><video style="display: none;"><![endif]-->
  <source media="(min-width: 1080px)" srcset="/assets/img/img-pc.jpg"/>
  <source media="(min-width: 768px)" srcset="/assets/img/img-tablet.jpg"/>
  <!--[if IE 9]></video><![endif]-->
  <img src="/assets/img/img-sp.jpg" alt="alt属性値を指定します。"/>
</picture>

まとめ

Picturefill.jsはフォールバックが必要になることもあり、完璧にレスポンシブイメージを再現してくるわけではありません。
ですが、レスポンシブイメージに対応したブラウザ(Globalで約86%)でパフォーマンスを最適化してくれるメリットは大きいと思います。
標準化された仕様にそっているので、複数人や作業をする場合でも認識を合わせやすくなります。

以下のドキュメント・記事を参考にしました。

お問い合わせはこちら