svg文書は有る意味,図形を描画する手順を記したスクリプトとも考えられる.canvgはこのアプローチのもと,svg画像をcanvas要素に描画するjavascriptライブラリである.本項ではこのcanvgの紹介と,レガシーie用ライブラリexplorercanvasと組み合わせてレガシーieを含めたクロスブラウザ上でsvgグラフィックを描く方法を示す.なお本項目は実験的要素が高く,実運用に耐えるとは考えられないため読み飛ばしても一向に構わない.
svg文書は有る意味,図形を描画する手順を記したスクリプトとも考えられる.canvgはこのアプローチのもと,svg画像をcanvas要素に描画するjavascriptライブラリである.本項ではこのcanvgの紹介と,レガシーie用ライブラリexplorercanvasと組み合わせてレガシーieを含めたクロスブラウザ上でsvgグラフィックを描く方法を示す.なお本項目は実験的要素が高く,実運用に耐えるとは考えられないため読み飛ばしても一向に構わない.
canvgはsvg記述の内容をcanvas要素における描画手続きに置き換えることでcanvas上にsvgグラフィックを描くというjavascriptモジュールであり,一見無駄なようにも思えるが,次に示す点で非常に有用である.
その他,a要素に依るハイパーリンク機能を実現したり,一部animate要素によるアニメーションにも対応しているなど,canvas要素でありながらsvg要素に見劣りしない機能を提供するなど,使い勝手はかなりのものである.
その一方でcanvas要素に特有な影響を受ける点に注意が必要だ.
このように必ずしも万能ではないが,svgの再利用性を高める効能を有しているため,憶えておいて損はない.
canvgを使うにはhtmlのhead要素にrgbcolor.jsとcanvg.jsを指定する.
<script type="text/javascript" src="rgbcolor.js"></script>
<script type="text/javascript" src="canvg.js"></script>
後はjavascript上からcanvgメソッドを呼び出すだけで良い.
canvgは機能が絞られている分,提供するapiも非常に明快である.
最も簡単な例を示そう.外部svgファイルをcanvas要素に描画してみた例だ.この時,もとのカンバスのサイズは200px×200pxであるが,何も指定しないとsvgに指定されたサイズ(ここでは200pt×200pt)に自動設定される.
svgにリンクを含む例.カーソルを円領域に合わせるとクリック可能となっているはずだ.
svgにアニメーションを含む例.複雑なものは無理だが,簡単なものなら勝手に実行してくれる.
パラメータを指定することで,canvas要素への描画を制御することが出来る.
下の例はマウスイベントを無視することでリンクが無効となった例.
アニメーションをキャンセルした例.先ほどと同じsvgではあるがアニメーションが無効となっている.
ignoreDimensions:trueを指定すると,カンバスの大きさに合わせて表示される.
ignoreClear:trueを指定することで,canvasに描画されている内容を消去せず,svgグラフィックを上に重ねることができる.
マウスイベントの捕捉,アニメーション処理は何もしない限り有効となってしまうが,これらの処理は内部でイベント処理,タイマーを使ってcanvasを書き換えている.従って,canvas要素に単にsvgを描画する場合や,canvasの内容が勝手に書き変わってしまうと言った問題がある場合は{ignoreMouse:true, ignoreAnimation:true, ignoreDimensions:true, ignoreClear:true}を指定すると良い.
svgを描画する位置を指定することも可能だ.以下は描画位置・サイズに関わるパラメータである.
描画位置を指定した例.カンバスサイズの変更が無効となる.
描画サイズを指定した例.svgのサイズをピクセル値に変換し,そのピクセル数でcanvas領域を分割する.この幅(高さ)をスケール値の単位として,scaleWidth,scaleHeightを指定する.実際の表示幅を指定するわけではないので注意が必要.
また,canvgでのsvg描画処理に関わるコールバック関数を設定することでより柔軟な処理を行うことが可能である.
forceRedrawに関数を登録しておくと,フレーム毎の再描画処理を行うかどうかを制御することが出来る.trueを返すとsvg画像からcanvasを再描画する.なお無闇にtrueを返す関数を渡すとシステムに無用な負荷がかかるので,この機能は良く良く考えた上で利用したほうが良い.
上記の処理はgetContext("2d")で得られるコンテキストオブジェクトから行うことも可能となっている.
なお,canvgが対応しているsvgの機能については,公式配布サイトにおいて多数のサンプルを提供しているため,そちらも参考とされたい.
canvgはcanvas要素に対応した環境で,svgを描画する機能を提供することを見た.ところでie8以前のレガシーieにおいてもexplorercanvasを導入することで,擬似的にcanvas要素を利用することが可能となるが,実はこの2つのライブラリを組み合わせることでsvgを画面上に描画することが可能となる.このページをie8で開いてみて欲しい.多少の不具合はあるものの,canvas要素にグラフィックが描かれていることだろう.
explorercanvasはvmlを用いてcanvas要素をエミュレートするライブラリである.インターフェースは一部未実装であるものの,web標準に準拠した物となっており,canvgと組み合わせてもそれほど問題は発生しない.従って,canvgにsvgを渡すと,canvgは内部で擬似canvas要素にsvgを描画するように指示し,擬似canvas要素は内部でvmlに変換するといったフローを執る.このように,スクリプトモジュール毎に役割が分かれているものの,行なっていることはsieと全く同じである.
従って,この動作を応用することでobject要素や,インラインでのsvgグラフィックの描画が可能となる筈であり,この方針に従って実装したものが次のスクリプトだ.ファイルはこちら.このスクリプトと,excanvas.js,rgbcolor.js,canvg.jsを読みこませることで,ほぼすべてのブラウザでsvgを描画することが可能となる.
/*
およそかなりのブラウザでsvgを表示可能とするスクリプト.
ie6,ie7,ie8ではvmlとして描画する.
android2.3.3brouserではcanvasに描画する.
firefox,chrome,safari,operaではsvgとして描画する.
生成した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();
}
})();
以下にsvgのサンプルを掲載する.ie8で見て欲しい.やはりie上でのsvg表示に特化したsieの方が良い結果が得られている.従って,このテクニックを用いる場合は,機能性を犠牲にしてでもとりあえずレガシーieでも何らかのsvgを表示させたい場合に用いるべきであり,間違ってもミッションクリティカルな用途には向かない.また,なお大体次の条件を満たすようにsvgを構成するとうまくいくようだ.
このように非常に条件がきついので,残念ながら利用する機会はそれほど無いかもしれない.