2014年2月1日土曜日

svgの要素とhtmlの要素とを混在させるとどうなるか

表題の問題,実はとっても厄介で,ルールに外れた記述を行うと途端に予想外の動作をしてしまうよ,という啓発記事を書きます.
※例によって個人調べですので間違いがあるかも知れません.
※2014/04/14ああなんだパージングって...orz... パーシングでしょうに


元来htmlとは別の仕様だったsvg


現在でこそhtml5の一仕様となったsvgですが,本来は(将来的に統合されることを期待しつつも)htmlとは全く別の規格でした.ですから無理矢理その内容を一本化することで様々な面で歪みが発生することとなってしまいました.従って,われわれweb技術の利用者側もその歪みについて理解しておかないと思わぬ落とし穴に嵌ることになります.

htmlのパーシング


前置きはさておき,突然ですがhtml文書がwebブラウザによって読み込まれた後,それはどのようにしてメモリ上に展開されるのでしょうか?ブラウザ毎にバラバラなのでしょうか?
結論から言うとそんなことは微塵も無く,きちんとした仕様として定められています.

12.2 Parsing HTML documents
http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html

この仕様はhtml文書を読み込んだ後,その内容をどのようにdomツリーとして展開すべきかについて定めており,(xmlと比較して)非常にルーズな記述を許しているhtmlの動作を統一している重要なものです.大げさですが,この仕様があるからブラウザ間の動作の互換が取られていると言っても良いくらいです.

パーシング仕様の中身


では具体的にどういった仕様が定められているのでしょうか?大雑把に言うと次のとおりです.
  1. 記述が正しかった場合のdom展開法
  2. 記述に誤りがあった場合のフォールバック法
もう少し詳しく言うと,
  • 要素名毎に名前空間(xhtml/svg/mathml)を振り分ける
  • 属性名の大文字・小文字等を揃える
  • 誤った位置に存在する要素のリプレース
  • 閉じられていない要素に対する対処策
  • e.t.c
と言ったもので,事細かく延々と記述されています.内容をざっと見ても判るのですが,既に非推奨となっている要素への対処なども含まれており大変カオスなことになっています
※ぶっちゃけhtmlを使う側にとってはあんまり読みたくない内容ではあります.私もざっとしか目を通していません(死)
(うろ覚えですが,この仕様は元々あったわけでなく,下位互換性を保つ目的で古いブラウザの実際の挙動から仕様を書き起こしたもの…だったような.)

svgへの影響


さて,xmlを基としていることにより本来厳密な記述が要求されるsvgですが,htmlにおけるインラインsvgでは先ほどの仕様の影響を受けます.つまり,次のような現象が発生します.
  • 名前空間の指定を行う必要がない
  • 属性名の大文字小文字を意識する必要がない
  • 要素を閉じ忘れると勝手に閉じられたことになる
  • svg要素内に記述された”変な”要素の挙動がアレ
前二つはsvgの記述が簡単になるからいいとしても,問題となるのが後ろの二つです.要するにソースコード上の見た目のツリー構造とdomに展開されたツリー構造との間に大きな隔たりが発生してしまうのです. 特に4つ目の動作は深刻で,たとえ文法上全く問題無くともdomツリーの再構成が発生します
※3つ目のも時折洒落にならないバグを引き起こしますが…

svgコンテキスト中での不明な要素の取り扱い


それでは”変な” 要素とはどのような要素なのでしょう.ここでは次の条件を満たすものとします.
  • svg要素配下(svgコンテキスト)に存在し,svg仕様で定義されていない名称の要素
本来このような要素がスタンドアロンのsvg文書内で見つかった場合,webブラウザはそれを(SVGUnknownElementオブジェクトに割り当て,)dom上に存在しつつも画像描画処理時には無視します.

しかしhtmlパージング仕様では次のような追加の処理が為されます.
  • htmlに同名の要素†が存在する場合,svg要素の直後に存在するものとしてdom構造を書き換える
  • そうでない場合無視する.
†正確にはhtmlの中の特定の要素が該当します.
例を示します.

<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
 <style>
svg{background-color:yellow;width:400px; height:20px;display:block;border:solid 1px black;}
svg div{color:red;}
svg~div{color:blue;}
 </style>
 </head>
 <body>
  <svg>
   <div>svg直下のdiv</div>
  </svg>
  <svg>
   <hoge>見知らぬ要素</hoge>
  </svg>
  <svg>
   <foreignObject width="100%" height="100%">
    <div>foreignObject直下のdiv</div>
   </foreignObject>
  </svg>
 </body>
</html>

ここでは3つの例を提示しています.1つ目がsvgコンテキストにdiv要素を配置した例で2つ目がsvgでもhtmlでも定義されていないhoge要素を配置した例です.
※3つ目は後で説明します.
これをブラウザ上で実際に表示してみましょう.

別ウインドウで表示


いかがでしたか?
1つ目のdiv要素の文字の色がコード上では赤くなりそうなところが実際には青くなってしまっているはずです.つまりhtmlパーサーが勝手にdomを書き換えてしまっているのです.実際にツールを使ってdom構造を確認してみると,firefoxでは下のようになっていました.(chromeでも同様です)

このように,いかなソース上で正しくともdomでは異なる形状に展開されることがあり得ます.よってこの仕様を知らないと「何故にソースコードとメモリ上のツリー構造が違うんじゃー」と悩む羽目に陥ります.

svgコンテキストにhtml要素を正しく挿入するには


では,svg要素配下にhtml要素を挿入することは出来ないのでしょうか?
実は出来ます. それが先ほどの3つ目の例で,svgの下に一旦foreignObject要素を噛ませてその中にdiv要素を配置するのです.foreignObject要素はsvgとは別のレンダラを使ってグラフィックを描画する役割を担っており,こうすることでsvgグラフィックの一部にhtmlを挿入することが可能となります.
この場合,domツリーの構造はそのまま維持されるため,cssによるスタイル指定も上手く動作します.
※但しforeignObject要素も万能ではなく,環境によっては上手く動作しないパターンがあるなど扱いには要注意です.(決まった仕様が存在しないため)

まとめ

  • svgはhtmlとは異なる仕様だった.
  • そのため,混在させると時折意味不明な挙動を引き起こす.
    そしてそれはバグではなく仕様.
  • svgにhtmlを挿入する場合はforeignObject要素を使う

応用


この動作を逆手に取ると,スクリプトを記述せずにsvgのフォールバック機構を実装することができます.

スクリプトレスなsvgのフォールバック手法・まとめ
http://defghi1977-onblog.blogspot.jp/2013/01/svg_21.html

0 件のコメント:

コメントを投稿