svg要素の基本的な使い方まとめ

Written by defghi1977@xboxLIVE.この文書は全てテキストエディタで作成しています.えーと,そりゃもうゴリゴリと.

22.canvgを用いたcanvas要素との連携

svg文書は有る意味,図形を描画する手順を記したスクリプトとも考えられる.canvgはこのアプローチのもと,svg画像をcanvas要素に描画するjavascriptライブラリである.本項ではこのcanvgの紹介と,レガシーie用ライブラリexplorercanvasと組み合わせてレガシーieを含めたクロスブラウザ上でsvgグラフィックを描く方法を示す.なお本項目は実験的要素が高く,実運用に耐えるとは考えられないため読み飛ばしても一向に構わない.

canvgによるcanvas要素へのsvgの描画

canvgはsvg記述の内容をcanvas要素における描画手続きに置き換えることでcanvas上にsvgグラフィックを描くというjavascriptモジュールであり,一見無駄なようにも思えるが,次に示す点で非常に有用である.

その他,a要素に依るハイパーリンク機能を実現したり,一部animate要素によるアニメーションにも対応しているなど,canvas要素でありながらsvg要素に見劣りしない機能を提供するなど,使い勝手はかなりのものである.

その一方でcanvas要素に特有な影響を受ける点に注意が必要だ.

このように必ずしも万能ではないが,svgの再利用性を高める効能を有しているため,憶えておいて損はない.

canvgの使い方

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も非常に明快である.

canvg
canvas要素にsvgの内容を描画する.
第一引数にはcanvas要素のidもしくはHTMLCanvasElementを指定する.
第二引数にはsvg画像へのURL文字列もしくはsvg記述の文字列を指定する.
第三引数には描画する際のパラメータを指定する.
canvg(canvasId|canvasElement, svgURL|svgString, [switch])

最も簡単な例を示そう.外部svgファイルをcanvas要素に描画してみた例だ.この時,もとのカンバスのサイズは200px×200pxであるが,何も指定しないとsvgに指定されたサイズ(ここでは200pt×200pt)に自動設定される.

svgにリンクを含む例.カーソルを円領域に合わせるとクリック可能となっているはずだ.

svgにアニメーションを含む例.複雑なものは無理だが,簡単なものなら勝手に実行してくれる.

パラメータを指定することで,canvas要素への描画を制御することが出来る.

ignoreMouse
boolean:trueを指定するとマウスに依るイベント処理を無視する.
ignoreAnimation
boolean:trueを指定するとアニメーション処理を無視する.
ignoreDimensions
boolean:trueを指定するとcanvas要素のサイズをsvg画像のサイズに合わせない.
ignoreClear
boolean:trueを指定するとsvg描画処理前にカンバスをクリアしない.(既存グラフィックにsvg画像を重ねる)

下の例はマウスイベントを無視することでリンクが無効となった例.

アニメーションをキャンセルした例.先ほどと同じsvgではあるがアニメーションが無効となっている.

ignoreDimensions:trueを指定すると,カンバスの大きさに合わせて表示される.

ignoreClear:trueを指定することで,canvasに描画されている内容を消去せず,svgグラフィックを上に重ねることができる.

マウスイベントの捕捉,アニメーション処理は何もしない限り有効となってしまうが,これらの処理は内部でイベント処理,タイマーを使ってcanvasを書き換えている.従って,canvas要素に単にsvgを描画する場合や,canvasの内容が勝手に書き変わってしまうと言った問題がある場合は{ignoreMouse:true, ignoreAnimation:true, ignoreDimensions:true, ignoreClear:true}を指定すると良い.

svgを描画する位置を指定することも可能だ.以下は描画位置・サイズに関わるパラメータである.

offsetX
int:svgを描画する基準位置.ピクセル値.
offsetY
int:svgを描画する基準位置.ピクセル値.
scaleWidth
int:svgを描画するスケール幅.(≠描画幅)
scaleHeight
int:svgを描画するスケール高.(≠描画高)

描画位置を指定した例.カンバスサイズの変更が無効となる.

描画サイズを指定した例.svgのサイズをピクセル値に変換し,そのピクセル数でcanvas領域を分割する.この幅(高さ)をスケール値の単位として,scaleWidth,scaleHeightを指定する.実際の表示幅を指定するわけではないので注意が必要.

また,canvgでのsvg描画処理に関わるコールバック関数を設定することでより柔軟な処理を行うことが可能である.

renderCallback
function:描画が完了した際に呼び出される関数.
forceRedraw
function:trueを返すと再描画処理が行われる.何も考慮せずtrueを返すような関数を渡すと負荷が高くなるため,注意する.

forceRedrawに関数を登録しておくと,フレーム毎の再描画処理を行うかどうかを制御することが出来る.trueを返すとsvg画像からcanvasを再描画する.なお無闇にtrueを返す関数を渡すとシステムに無用な負荷がかかるので,この機能は良く良く考えた上で利用したほうが良い.

上記の処理はgetContext("2d")で得られるコンテキストオブジェクトから行うことも可能となっている.

ctx.drawSvg
svgを直接描画する.HTMLCanvasElement.getContext("2d")で得られるCanvasRenderingContext2Dオブジェクトの拡張メソッド.
ctx.drawSvg(svgUrl|svgString,offsetX,offsetY,scaleWidth,scaleHeight)
各パラメータの意味はcanvgメソッドにおけるものと同じ.これ以外のignoreMouse等のフラグ値は全てtrueが設定されているものとして扱われる.

なお,canvgが対応しているsvgの機能については,公式配布サイトにおいて多数のサンプルを提供しているため,そちらも参考とされたい.

レガシーIEへの応用

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を構成するとうまくいくようだ.

このように非常に条件がきついので,残念ながら利用する機会はそれほど無いかもしれない.

0 1 1 2 曇天 本日は晴天なり 文字の描画される基準ライン 本日は晴天なり 本日は晴天なり 本日はなり 寿限無、寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末食う寝る処に住む処やぶら小路の藪柑子パイポパイポパイポのシューリンガンシューリンガンのグーリンダイグーリンダイのポンポコピーのポンポコナーの長久命の長助 グラデーション方向 ここがクリップ領域 _replace _self _parent _top _blank