2012年6月23日土曜日

sie用のインラインsvg表示スクリプトを作ってみた

ieでsvgを描画することができるライブラリsieですが,インラインsvgの表示は結構面倒です.

sieではsvg要素をscript要素で囲んだものをインラインsvgとして描画することになっていますので,sieの処理が走る前に(1)svgの中身を取り出し,(2)script要素に設定して,(3)元のsvg要素のあった場所にscript要素を挿入することになります.

しかしieではこのsvg要素の中身を取得するのが困難で,sieが標準的に配布しているsvginhtml.jsというスクリプトファイルでは,svg要素の中身を取得するのにbody要素全体をscript要素化するといったトリッキーな処理を行なっています.

これはieにおいてsvg要素をdocument.getElementsByTagNameメソッドで取得できないのが原因なのですが,実はこれを回避する方法があります.


このテクニックはHTML5 Shivと呼ばれており,html5で追加された要素をレガシーieにてスタイルを指定可能とするために用いられます.かいつまんで言うと,事前に「document.createElement("article");」を実行しておけば,スタイルシートでarticle要素にアクセスすることが可能となるのです.

この挙動を参考とし,svg要素に応用できないか試してみたのが次のコードです.事前にsvgに対応するElementを生成すると,実にそれ以降document.getElementsByTagNameメソッドを使ってsvg要素にアクセスすることが可能となります.

但し,ieのinnerHTMLプロパティは次のような余計な処理をしてしまいます.
  • 要素の名称を全て大文字に変更してしまう.
  • 属性の値の二重引用符を外してしまうケースがある.
いずれもxmlの記述としては正しくないため,修正を行う必要があります.

単にbody要素の構造を操作しているだけなので, svginhtml.jsよりも制御しやすいと思います.

[使い方]
下記のコードをsie.jsを宣言してhtmlファイル内部で実行する.
※sie.jsは事前にインラインsvg用の修正を施しておくこと.
※ie8では動作を確認しましたが,ie6などではエラーが発生する?

サンプルはこちら

/*
sie用インラインsvg表示スクリプト
*/
//http://ejohn.org/blog/html5-shiv/
document.createElement("svg");
window.onload = function(){
 //svg要素名称のマッピング
 var elementNameMap = {
  "A" : "a",
  "ALTGLYPH" : "altGlyph",
  "ALTGLYPHDEF" : "altGlyphDef",
  "ALTGLYPHITEM" : "altGlyphItem",
  "ANIMATE" : "animate",
  "ANIMATECOLOR" : "animateColor",
  "ANIMATEMOTION" : "animateMotion",
  "ANIMATETRANSFORM" : "animateTransform",
  "CIRCLE" : "circle",
  "CLIPPATH" : "clipPath",
  "COLOR-PROFILE" : "color-profile",
  "CURSOR" : "cursor",
  "DEFS" : "defs",
  "DESC" : "desc",
  "ELLIPSE" : "ellipse",
  "FEBLEND" : "feBlend",
  "FECOLORMATRIX" : "feColorMatrix",
  "FECOMPONENTTRANSFER" : "feComponentTransfer",
  "FECOMPOSITE" : "feComposite",
  "FECONVOLVEMATRIX" : "feConvolveMatrix",
  "FEDIFFUSELIGHTING" : "feDiffuseLighting",
  "FEDISPLACEMENTMAP" : "feDisplacementMap",
  "FEDISTANTLIGHT" : "feDistantLight",
  "FEFLOOD" : "feFlood",
  "FEFUNCA" : "feFuncA",
  "FEFUNCB" : "feFuncB",
  "FEFUNCG" : "feFuncG",
  "FEFUNCR" : "feFuncR",
  "FEGAUSSIANBLUR" : "feGaussianBlur",
  "FEIMAGE" : "feImage",
  "FEMERGE" : "feMerge",
  "FEMERGENODE" : "feMergeNode",
  "FEMORPHOLOGY" : "feMorphology",
  "FEOFFSET" : "feOffset",
  "FEPOINTLIGHT" : "fePointLight",
  "FESPECULARLIGHTING" : "feSpecularLighting",
  "FESPOTLIGHT" : "feSpotLight",
  "FETILE" : "feTile",
  "FETURBULENCE" : "feTurbulence",
  "FILTER" : "filter",
  "FONT" : "font",
  "FONT-FACE" : "font-face",
  "FONT-FACE-FORMAT" : "font-face-format",
  "FONT-FACE-NAME" : "font-face-name",
  "FONT-FACE-SRC" : "font-face-src",
  "FONT-FACE-URI" : "font-face-uri",
  "FOREIGNOBJECT" : "foreignObject",
  "G" : "g",
  "GLYPH" : "glyph",
  "GLYPHREF" : "glyphRef",
  "HKERN" : "hkern",
  "IMAGE" : "image",
  "LINE" : "line",
  "LINEARGRADIENT" : "linearGradient",
  "MARKER" : "marker",
  "MASK" : "mask",
  "METADATA" : "metadata",
  "MISSING-GLYPH" : "missing-glyph",
  "MPATH" : "mpath",
  "PATH" : "path",
  "PATTERN" : "pattern",
  "POLYGON" : "polygon",
  "POLYLINE" : "polyline",
  "RADIALGRADIENT" : "radialGradient",
  "RECT" : "rect",
  "SCRIPT" : "script",
  "SET" : "set",
  "STOP" : "stop",
  "STYLE" : "style",
  "SVG" : "svg",
  "SWITCH" : "switch",
  "SYMBOL" : "symbol",
  "TEXT" : "text",
  "TEXTPATH" : "textPath",
  "TITLE" : "title",
  "TREF" : "tref",
  "TSPAN" : "tspan",
  "USE" : "use",
  "VIEW" : "view",
  "VKERN" : "vkern"
 };
 var svgs = document.getElementsByTagName("svg");
 for(var i = 0, len = svgs.length; i<len; i++){
  var svg = svgs[0];
  var source 
   = "<svg" 
   + toAttributesNotation(svg) 
   + ' xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
   + fixHTML(svg.innerHTML)
   + "</svg>";
  var script = document.createElement("script");
  script.type = "image/svg+xml";
  script.text = source;
  var parent = svg.parentNode;
  parent.replaceChild(script, svg);
 }
 
 //svg要素の属性を属性記述に変換する.
 function toAttributesNotation(svg){
  var attrs = svg.attributes;
  var result = "";
  for(var i=0, len=attrs.length; i<len; i++){
   var attr = attrs[i];
   if(attr.value == "null"){
    continue;
   }
   result += (" " + attr.name + '="' + attr.value + '"');
  }
  return result;
 }
 
 //勝手に変えられてしまったinnerHTMLを元に戻す.
 function fixHTML(html){
  var result = html;
  //属性の値を二重引用符で囲む.
  result = result.replace(/\s([a-z|A-Z|0-9|-]+?)=([^"]+?)([\s|>])/g,' $1="$2"$3');
  //大文字に変換されてしまった要素名を正しいものに直す.
  result = fixTagName(result);
  return result;
 }
 
 //タグの名称を修正する.
 function fixTagName(text){
  var result = text;
  var regex = /<([A-Z|-]+?)[\s|>]/;
  var match;
  while((match = result.match(regex)) != null){
   var tag = match[1];
   var fixed = elementNameMap[tag];
   if(!fixed){
    fixed = tag.toLowerCase();
   }
   result = result.replace("<" + tag, "<" + elementNameMap[tag]);
   result = result.replace("</" + tag + ">", "</" + elementNameMap[tag] +">");
  }
  return result;
 }
 
}

0 件のコメント:

コメントを投稿