2013年1月21日月曜日

スクリプトレスなsvgのフォールバック手法・まとめ

svgが次第に広まりつつある中で,問題となるのがieとandroidです.これらは比較的最近バージョンですらsvgをサポートしないものがある上,かなりのシェアを占めているがために無視出来ない存在です.その為,これまではjavascriptを使ってブラウザ環境を調査し,svgのサポート状況によってsvg画像と代替のpng画像とを切り替えるといった処理を行なっていました.しかしこの方法ではjavascriptが動作しない環境ではどうしようもありません.

本記事ではスクリプトレスでこの画像の切り替えを実現させようというもので,svgの使い方のシナリオごとに対処策を探るものです.特に懸案だった背景画像におけるフォールバックが解決したため,ほぼすべてのケースで対処可能となりました.

※01/22補足)この記事はスタイルとともにスクリプトについてもう少し広がりそうなので,別記事としてまとめました.
※01/22記事の内容について検証を行なっています.動作的にはこれで良さそうなのですが,理屈の部分で・・・→解決しました.
※01/27補足)つづきを書きました.
※2014/08/15補足)コメントを追加
※2014/08/16リンクを追加

svgをサポートしないブラウザ


svgはweb上でベクタ画像を表示するもので,webのこれからを担うものとして注目を集めています.しかし,htmlでのサポートが確定したのが比較的最近と言う事もあり,ブラウザによってはsvgを表示することが出来ないものが存在します.例えば次のブラウザです.
  • internet exprolerのバージョン8以前のもの
  • android osのバージョン2以前の標準ブラウザ
これらは,依然として無視できないシェアを占めているため,svgの代わりの適切な代替画像(フォールバック画像)を表示させることが求められるケースが多いことでしょう.

しかし,画像ファイルの利用の仕方には様々なものがあり,一概にこうすれば良いというわけに行かないのが悩ましいところです.

svgの利用シナリオ


htmlページにsvg画像を挿入するには概ね次の4つの方法が挙げられるでしょう.
  1. インラインsvgの利用
  2. object要素による表示
  3. img要素による表示
  4. background-imageによる表示
従ってこの方法毎に対処策を考えねばなりません.

「インラインsvgの利用」の場合


そもそもこの方法はhtmlとsvgとを密に連携させることを目的としています.従ってこれをsvgをサポートしない環境から表示することに余り意味がないのですが,ブラウザがsvgをサポートしてないことをスクリーンに表示させたい場合もあるでしょう.

この場合はforeignObject要素を用いることで対処することができます.
この方法は以前拙ブログにて紹介しました.

<!DOCTYPE html>
<html>
 <body>
  <svg width="200px" height="200px">
   <rect x="50" y="50" width="100" height="100" fill="yellow"/>
   <foreignObject display="none">
    <img src="img.png"/>
   </foreignObject>
  </svg>
 </body>
</html>

このコードをsvgを解釈できないブラウザが読み込むと,中身のimg要素のみが有効となります.一方svgをサポートするブラウザはdisplay=noneを元にimg要素の描画を行いません.

※但しこちら(SVGをIE等のブラウザ対応を考慮して使う方法まとめ(SVGのフォールバック画像など))で指摘されているようにSVGをサポートするブラウザでは必要の無いimg要素が読み込まれてしまう点に注意しましょう.(ieだけなら条件付きコメントが使えるんだけれど)
→解決しました
HTMLパーサーを使ったインラインSVGのフォールバック方法
http://defghi1977-onblog.blogspot.jp/2014/08/htmlsvg.html

「object要素/img要素による表示」の場合


この場合はobject要素が標準的にもつフォールバック機構を利用しましょう.img要素でsvgを表示するのはフォールバックが面倒であるためおすすめしません.

<object data="hoge.svg"><img src="hoge.png"/></object>

※セキュリティの絡みからimg要素でsvg画像を表示したい場合は,先ほどの「インラインsvg」 を使ってsvgのimage要素からsvg画像を読み込むようにするとほとんど同じ効果が得られます.

<!DOCTYPE html>
<html>
 <body>
  <svg width="200px" height="200px">
   <image width="100%" height="100%" preserveAspectRatio="none" xlink:href="img.svg"/>
   <foreignObject display="none">
    <img src="img.png"/>
   </foreignObject>
  </svg>
 </body>
</html>

「background-imageによる表示」の場合


さて,本記事の主題です.上記の方法に比べcssのbackground-imageプロパティからsvg画像を読み込んだ場合,実は非常にフォールバックしにくいと言った問題があります.というのも,フォールバックを実現するには次の要件を充たす必要があるためです.
  • svgをサポートしない環境でのみ実行されるスタイルシートを作る
これが実現できれば,特定の環境でのみ背景画像を上書きすることができることになります. ではこのsvgをサポートしないということをどうすれば判定できるのでしょう?

※PCでの閲覧をメインに据えており,androidを無視して良いのであれば次のような方法があります.

svgをサポートしない環境でインラインsvgはどのように扱われるか?


その前にsvgをサポートしない環境でインラインsvgの挙動について考えてみましょう.
webブラウザは解釈できない要素を発見するとその要素を無視します.更にその中に理解可能な要素があった場合は,何事もなかったように描画処理を続行します.

先ほどのインラインsvgのフォールバック処理時では,svg要素自体は非表示としてあるにもかかわらず,ブラウザがその内容を理解できずに中身のimg要素が表示されることを利用していました.

ではこの仕組みを何とか利用することは出来ないでしょうか?

metadata要素にlink要素を仕込む


ここでsvgの機能をおさらいしておきましょう.metadata要素はsvg画像に任意のユーザーデータを挿入するための仕組みで,通常この要素の配下にある要素はsvgグラフィックの描画を行う際に無視されます.従って,htmlの要素を挿入しても仕組み上は問題ないはずです.

そこで,インラインsvg内のmetadata要素にスタイルシートを参照するlink要素を記述した場合,どのような結果となるでしょう.svgのサポート状況毎に考えてみましょう.コードとしては次のような形を取ります.

<html>
<body>
<svg>
 <metadata>
  <link href="css.css" rel="stylesheet" type="text/css"/>
 </metadata>
</svg>
</body>
</html>

svgをサポートする環境


svgをサポートする環境であれば,metadata要素内部のlink要素は描画処理に関係ないものとして無視されるでしょう.

svgをサポートしない環境


svgをサポートしない環境であれば,svg要素及びmetadata要素の存在が無視され,link要素がそこに存在するものとして扱われるでしょう.通常,link要素はhead要素の下に配置されるべきですが,その部分は目をつむって下さい.

このようにsvgのサポート状況でスタイルシートの切り替えができる事になります.

background-imageにおけるsvgのフォールバック手法


では実際にコードを記述してみましょう.



●メインとなるhtml文書
<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
 <style>
  div{
   height:100px;
   background-image:url("sample.svg");
  }
 </style>
 </head>
 <body>
  <svg display="none">
   <metadata>
    <link rel="stylesheet" href="fallback.css" type="text/css"/>
   </metadata>
  </svg>
  <div>
   この部分の背景はsvgのサポート状況により切り替わります.
  </div>
 </body>
</html>

●link要素が参照するスタイルシート.
div{
 background-image:url("fallback.png");
}

ポイントは次の通りです.
  • メインとなるhtml文書はいつもの通り作成する.背景画像はsvgとしておく.
  • svg-metadata-link要素を定義し,link要素から外部cssを参照する.
  • svg要素はレイアウトの邪魔とならないようにdisplay="none"を指定しておく.
  • 外部cssにはフォールバック画像を指定するスタイルを記述しておく.
これを実際に表示させた例を下に示します.上がfirefoxの結果で,下がie8での結果です.


このように正しく画像の切り替えが行われていることが判ります.

補足・metadata配下のstyle要素


ではlink要素の代わりにstyle要素を使えばよいではないか?とも考えました.が,試してみると上手く行きません.metadata要素の意味合いから上手くいって良さそうなのですが,metadata要素配下のstyle要素は有効になってしまいます.

ただこの動作はなんとなく説明が付きます.htmlでは名前空間の指定が必要ない代わりに,勝手に名前空間の解決がなされます.従ってmetadata要素配下のstyle要素はsvgのstyle要素として解釈されてしまうのでしょう.このことからmetadata要素は名前空間の指定可能なスタンドアロンのsvgやxhtmlにおけるインラインsvgで利用すべきものと考えられます.

つまりこのフォールバック動作はsvgにたまたまlinkという名称の要素が存在していないがために上手く行っていることになります.
そうではなく,svg要素の内部にlink要素が見つかった場合の動作がたまたま「無視」だったようです.

補足・動作の拠り所


この動作の拠り所となったhtmlの仕様は次の通りです.(多分これでいいはず)

動作検証を行った環境


firefox,chrome,opera,safari,ie10
ie6,ie8,android2

動作原理上,この他のブラウザでも動作するものと思われます.

0 件のコメント:

コメントを投稿