2012年6月26日火曜日

ほとんどすべてのブラウザでsvgを表示可能とするスクリプト

ちょっと言い過ぎだけれど,要は古いieとandroid osのブラウザに対応できればいいわけだから,それほど難しくはないはず.sieでインラインsvgを表示した時のスクリプト処理と,この前発見したexplorercanvasとcanvgを組み合わせてsvgをcanvas経由で表示させる処理とを組み合わせてやるだけだからなんとかなる!

・・・と思っていざ初めて見たものの,泣きたくなるくらいieの動作が酷い.もう洒落にならんね.
いろいろ分かったこと.
  • explorercanvasは中でvmlを使っている.
    svgのパーサーを担当するcanvgと組み合わせるとsvgからvmlを生成する結果となるので,やっていることはsieと全く変わらない.
  • ieのinnerHTMLは全く信用ならない.
    タグ内部の「/」を削除したり,要素名を大文字にしたり,属性値の引用符を削除したりと傍若無人だ.しかしなぜかsvg要素から直接innerHTMLを取得する場合は「/」 を削除しない.原因は不明.
  • android os のブラウザはcreateElementNSをサポートするが,svg要素を生成するとインターフェースがすっからかんである.よって何らかのプロパティやメソッドが定義されいているかいないかで他のブラウザと見分けることができる.
    ただこれは将来的にsvg要素がサポートされた時点で不要となるテクニックだ.
  • 同様にレガシーieはcreateElementNSそのものをサポートしないのでie9以降と区別するのに使えるはず.
  • レガシーieでiframeにsvgを参照させると,なぜかダウンロードダイアログが表示されてしまう.余計なことを…
使い方
  • javascriptライブラリ「explorercanvas」「canvg」を入手してください.
  • excanvas.js,rgbcolor.js,canvg.jsを読み込ませて,更に下記のスクリプトを読み込ませてください.
  • ブラウザによって自動的にグラフィック描画メソッドを切り替えます.
    • レガシーie…vml(擬似canvas要素)
    • firefox,chrome,opera,safari…svg
    • android os ブラウザ…canvas
  • window.onload後,ブラウザによりcanvas要素を生成してグラフィックを描画します.
  • iframe,object,embed,img,svg要素に対応しているはずです.
  • 自動生成したcanvas要素にはidとclassが設定されているので,この値を元にスタイルを紐付けてください.
実際に使う場合は,バグがあるかもしれないことを承諾した上で,使ってください.出典を削除しない限り,煮るなり焼くなりお好きにどうぞ.動作サンプル?そんなものは無いのでお好きに試してみてください.
http://www.h2.dion.ne.jp/~defghi/svgMemo/svgMemo_22.htm
追記)
このままだとかなーり重いので,canvgの実行フラグに下記を追加すると良いかもしれません.
  • ignoreMouse: true => ignore mouse events
    マウスイベントを捕捉してcanvas要素を書き換えているのを無効に.
  • ignoreAnimation: true => ignore animations
    アニメーション処理を行うタイマー処理を無効に.
  • (特にieだと内部でvmlが生成されているのでもっと重くなっているかも…)
追記2)
やっぱり,sieには勝てないということ.基本的な図形の描画以外はずだぼろ.事前準備と用途を限れば使えないこともないが,はっきり言って期待してはいけない.

/*
およそかなりのブラウザでsvgを表示可能とするスクリプト.
ie6,ie7,ie8ではvmlとして描画する.
android2.3.3brouserではcanvasに描画する.
firefox,chrome,safari,operaではsvgとして描画する.
ie9は?…まー動くんじゃね?

生成したcanvasにはidとclassが付与されるので,これを手がかりにスタイル付けしてください.
(処理簡略化のため,元のsvg要素のスタイルを引き継ぎません.)

必須ライブラリ
excanvas…レガシーieでcanvasapiを利用可能とするライブラリ.
rgbcolor…下のcanvgライブラリで利用する.
canvg…svgをcanvasapiを使って描画するライブラリ

This script is edited by DEFGHI1977 @xboxlive
*/
(function(){
 
 //レガシーieにはcreateElementNSが実装されていない.
 var isLegacyIE = document.createElementNS == undefined;

 if(isLegacyIE){
  //html5shivによりsvg要素にアクセス可能とする.
  document.createElement("svg");
  window.attachEvent("onload", loadSvg);
 }else{
  if(document.createElementNS("http://www.w3.org/2000/svg","svg").width){
   //svg要素が実装されているので何もしない.
   return;
  }
  //svg要素を生成し,インターフェースが実装されているかを確認することでアンドロイドか判定が可能.
  window.addEventListener("load", loadSvg, false);
 }
 
 //svgをcanvasに書き出す
 function loadSvg(){
  //メイン処理
  function main(){
   //要素ごとの処理
   drawSvg("iframe", toSource, true, false);
   drawSvg("object", toSourceObject, true, false);
   drawSvg("embed", toSource, true, false);
   drawSvg("img", toSource, true, true);
   if(isLegacyIE){
    drawSvg("svg", toSourceSvgIE, false, false);
   }else{
    drawSvg("svg", toSourceSvg, false, false);
   }
  }
  
  //canvgを使ってsvgを描画する.
  function drawSvg(tagName, toSourceFunc, refering, ignoreMouse){
   var elems = document.getElementsByTagName(tagName);
   var checker = refering ? svgchecker: nochecker;
   for(var i = 0, len=elems.length; i<len; i++){
    var elem = elems[i];
    var source = checker(toSourceFunc(elem));
    if(!source){
     continue;
    }
    var canvas = createCanvas(elem, tagName);
    var c = document.getElementById(canvas.id);
    try{
     canvg(canvas.id, source, {ignoreDimensions: refering, ignoreMouse: ignoreMouse});
    }catch(e){
     canvas.title = e.message;
    }
   }
  }
  
  //参照先の情報がsvgであることを確認するチェッカー.
  var selector = /.+\.svg/i;
  function svgchecker(source){
   var match = source.match(selector);
   if(match){
    return match[0];
   }else{
    return undefined;
   }
  }
  //何もしないチェッカー
  function nochecker(source){
   return source;
  }
  
  //canvas要素を挿入する.
  var CANVAS = "canvas";
  var WIDTH = "width";
  var HEIGHT = "height";
  var FROM = "from_";
  var C_CANVAS = "canvas_";
  var STYLE = "style";
  var NONE = "none";
  var NULL_STR = "";
  var count = 0;
  var num = /\d+/;
  function createCanvas(elem, tagName){
   var canvas = document.createElement(CANVAS);
   
   var width = elem.getAttribute(WIDTH);
   var height = elem.getAttribute(HEIGHT);
   
   try{
    width = width!=NULL_STR ? ~~(NULL_STR + width).match(num)[0] : 300;
   }catch(e){
    width = 0;
   }
   try{
    height = height!=NULL_STR ? ~~(NULL_STR + height).match(num)[0] : 300;
   }catch(e){
    height = 0;
   }
   
   canvas.setAttribute(WIDTH, width);
   canvas.setAttribute(HEIGHT, height);
   //http://www.html5.jp/blog/?p=22
   if(isLegacyIE){
    canvas = G_vmlCanvasManager.initElement(canvas);;
   }
   //
   canvas.id = C_CANVAS + (elem.id!=NULL_STR ? elem.id : count++);
   canvas.className = FROM + tagName;
   canvas.title = elem.title;
   elem.style.display = NONE;
   elem.parentNode.insertBefore(canvas, elem);
   return canvas;
  }
  
  //svg情報を取得する.(iframe,embed,img要素用)
  function toSource(elem){
   var source = elem.src;
   elem.src = "";
   return source;
  }
  //svg情報を取得する.(object要素用)
  function toSourceObject(elem){
   var source = elem.data;
   elem.data = "";
   return source;
  }
  //svg情報を取得する.(svg要素用)
  var div = document.createElement("div");
  function toSourceSvg(elem){
   var clone = elem.cloneNode(true);
   div.appendChild(clone);
   var result = div.innerHTML;
   div.removeChild(clone);
   return result;
  }
  
  //svg情報を取得する.(svg要素用)(ie専用)
  //NOTE:svg要素を別要素で囲んでinnerHTMLを
  function toSourceSvgIE(elem){
   //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"
   };
   return "<svg" + toAttributesNotation(elem) + ">" + fixHTML(elem.innerHTML) + "</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;
   }
  }
  main();
 }
})();

0 件のコメント:

コメントを投稿