WWW Watch

SVG のフォールバックは結局 Modernizr に落ち着いた件

SVG に対応していない環境に対するフォールバックの方法に関するお話。SVG を img 要素で読み込んでおいて、Modernizr と jQuery で未対応ブラウザに対しては代替画像に差し替えるという手法の説明と、関連する基本的な知識について。

SVGタイトルでほぼ言い切ってる感じなんですが......

SVG に対応していない環境 (IE8 以前とか、旧 Android の標準ブラウザとか) に対するフォールバックの方法はいくつかありますが、色々試して、最終的に img 要素で SVG を配置した上で、Modernizr と簡単な JavaScript (とりあえず jQuery) 書いて SVG 未対応ブラウザに対しては代替画像に差し替えるっていう手で落ち着きましたというお話。

SVG を使うにあたって、どの程度古いブラウザまで対応するかっていうのはありますが、今回対象にしている SVG は、ページ内で通常の画像 (普通の JPEG とか PNG) と同じように使われるものを想定していますので、いくら古いブラウザはサポート対象外とは言っても、文書内で大切な意味を持つ画像が表示されないのはまずいだろと。

あわせて対象となる SVG 内では、外部画像の読み込み、イベント処理、スクリプトの実行などを行っていないという前提になります。通常の Web サイト制作で扱うことが多い SVG は単純に SVG 画像として表示させればよいものがほとんどですからね。

HTML 文書への SVG の埋め込み

少し基本的な話に戻りますが、SVG を HTML に埋め込む方法はいくつかあります。例えば下記のような感じ。

  1. SVG 要素で直接埋め込む
  2. object 要素を使用する
  3. iframe 要素を使用する
  4. embed 要素を使用する
  5. img 要素を使用する
  6. CSS の background-image プロパティを使用する
  7. input type="image" による埋め込み
  8. picture 要素を使用する (最後の方におまけで書いています)

SVG 要素で直接埋め込む場合

HTML5 では SVG 要素によって直接 HTML 文書内に SVG を記述してしまえば埋め込みは可能ですが、メンテナンスが面倒なので通常は使用しません。前に公開した制作ガイドラインでも、SVG 要素による埋め込みはなしってことにしています。

ですので、SVG 要素内に display="none" した foreignObject 要素を置き、その中に img 要素によって代替画像を置く方法 (下記サンプルソース) で、SVG 要素に未対応のブラウザに対するフォールバックとすることはできるんですが、前述した理由でこの方法はなしと。

<svg width="400px" height="300px" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
  <!-- ここに SVG ソースコード -->
    <foreignObject display="none">
      <img src="fallback.png" alt="画像だよ" />
    </foreignObject>
</svg>

object 要素を使用する場合

次に、object 要素で埋め込む場合なら、下記のような感じで img 要素をフォールバックにすることはできます。

<object data="sample.svg" type="image/svg+xml">
 <img src="fallback.png" alt="画像だよ" />
</object>

ただ、object 要素は (iframe 要素も同様ですが) 新たにインラインフレームを生成し、その中に指定された外部リソースを読み込む形になりますので、画像に対してリンクを張りたい場合に、a 要素内に object 要素を置いたとしても、そのままでは画像をクリックしてもクリックイベントが object 要素内のインラインフレーム側で処理されてしまい、a 要素のクリックとしては処理されません。

object 要素がインラインフレームを生成している件は、object 要素で読み込む SVG 内で a 要素によってリンクを張ってみると、クリック時にそのリンクが object 要素内に展開されることからもわかりますが、このままだと画像にリンクが張れなくなっちゃう。

で、これを回避するためには、

  • object 要素に pointer-events: none; してインラインフレーム側でのイベント発生を止めといて
  • object 要素を内包する a 要素に対して display: inline-block; なり、display: block; なりしてクリッカブルな領域を確保する

ていう合わせ技で対応できますが、いちいちダルいのでなし。要素的にはフォールバックの仕組みもあって object 要素が一番適してはいるんですけどね。もうこの辺は好みの問題になっちゃいますね。

iframe / embed 要素を使用する場合

iframe 要素の場合も、object 要素と同様に、画像に対してリンクを張りたい場合の問題があるのと、そもそも a 要素の子要素としてインタラクティブ・コンテンツの iframe 要素が配置できないため無理 (object 要素も usemap 属性がある場合はインタラクティブ・コンテンツです)。それにリンクを張る / 張らないで使用する要素を変えるのとかダルいのでこれもなし。

embed 要素も同じですが、そもそも embed 要素にはフォールバックの仕組みがないので無理というか、まぁそれを言ったら img 要素もフォールバックの仕組みはないですけども、わざわざ embed 要素を使う意味もないのでなし。

CSS の background-image プロパティを使用する場合

CSS の background-image プロパティを使用するのは、使用する場所が背景画像であれば必然的にそうなりますし、フォールバックの方法もありますが、別に背景が表示されなくても困ることはない (困ることはないというか、困らないように作るのが普通) ので、わざわざフォールバックを用意しません。

結局 img 要素を使用することに

ということで、最初に書いた結論に至るわけですが、最終的に img 要素を使うのが妥当ということになりました。

あとは img 要素でどうやってフォールバックを提供するかですね。img 要素には object 要素などのようにフォールバックの仕組みがありませんから。

で、JavaScript でやろうということになるわけですね。もちろん、SVG 未対応 + JavaScript 無効という環境では画像が表示されなくなる問題は残るものの、もうそこはきちんと代替テキスト入れとくことで許してもらおうということで。

Modernizr を利用したフォールバックの手法

前置きが長くなりましたが、やってることは簡単です。Modernizr で SVG 対応しているかを調べて、していなかったら img 要素の src 属性値を代替画像のものに差し替えるっていうやつ。手順としては下記の通り。

  1. フォールバック用の画像を SVG と同ファイル名の .png 形式で用意し、SVG と同階層に設置する
  2. Modernizr Download Builder から SVG 判別ライブラリをダウンロード
  3. 上記を modernizr-svg.js などとして保存し、Web ページに読み込み
  4. 下記のソースコードを modernizr-svg.js の後ろで読み込み (jQuery を使っている前提)
$(function(){
  if (!Modernizr.svg){
    $('img').each(function() {
      $(this).attr('src', $(this).attr('src').replace(/\.svg/gi,'.png'));
    });
  }
});

正規表現の部分はもうちょい簡素に書いても実用上は問題ないと思いますが一応。

Modernizr に関しては、SVG の判定だけできればいいので、SVG 以外のチェックは外して最低限にしておきます。

Modernizr Download Builder から SVG 判別ライブラリをダウンロード

これで未対応ブラウザに関しては Web ページ内で img 要素で読み込まれている .svg ファイルが全部、.png ファイルに差し替えられるので、とりあえずフォールバックとしては十分機能するかと思います。

おまけ: picture 要素使ってみるとかどう?

下記のように、picture 要素でやっとくと、picture 要素に対応していない古いブラウザに関してはフォールバックの img 要素が表示されるのでいいんじゃね? とか思ったんですが、そもそも picture 要素に対応しているブラウザが現状では少なすぎて (IE と Firefox が未対応) ダメだった件。

<picture>
  <source srcset="sample.svg" type="image/svg+xml" />
  <img src="fallback.png" alt="画像だよ" />
</picture>

対応するブラウザさえ増えたら、これが一番きれいな気がするのでいいんじゃないかと思うんですが、IE が 9 以降でせっかく SVG に対応しているのに、picture 要素でやっちゃうとずーっと代替画像しか表示されないってのはちょっとねぇ...... 残念です。

余談: SVG の 参照モードと処理モード

svg では参照モード (Referencing modes) と処理モード (Processing modes) によって、SVG がどのように扱われるか、例えば SVG 内での外部リソースの参照や、スクリプトやアニメーションの実行可否などが決まるようになっています。SVG を埋め込む手法によって動作に影響を与えますので、知っておくといいと思います。今回のように単純な画像として使うだけなら別にいいんですけどね。

仕様は下記にあります。

松田氏の下記スライドなんかはその辺を含めて一通り理解するのにいいんじゃないでしょうか。

関連エントリー

Recent Entry

全ての記事一覧を見る

Hot Entry

逆引きおすすめエントリー