この記事は「SVG Advent Calendar 2014」の8日目の記事です。前日の記事も私が書いてまして、「svg.jsとsvg.draggable.jsを使って、SVGタグ内のオブジェクトをドラッグ&ドロップさせてみる」としてまとめています。
この記事では、サーバーを挟まずクライアント側の処理だけで、SVGタグを画像化させるということを実現しています。
やり方は簡単でcanvgを使います。canvgはsvgをパースしcanvas化することができるツールです。canvas化してしまえば「toDataURL()」が使えるので、クライアント側だけで処理が完結するという仕組みです。
実際のコードはこんな感じで、canvg関数にcanvasのDOMオブジェクトとsvgタグを引数として渡し、コールバック関数(renderCallback)の中で、canvasのtoDataURL()を呼び出すだけです。カンタン!!(※もちろん、svg.jsとjQueryは使わなくても書けますよ。)
<head>
<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/svg.js/dist/svg.js"></script>
<script src="bower_components/canvg/dist/canvg.js"></script>
<!-- endbower -->
<!-- endbuild -->
</head>
<body>
<svg id="drawing" style="border:solid 1px;"></svg>
<div><button id="button">convert to image</button></div>
<script>
var draw = SVG('drawing').size(500, 500);
var image = draw.image('images/NKJ56_gatsudanshi500.jpg', 150, 100).dx(30).dy(30);
var text = draw.text('SVGを画像化できますか?').move(150, 150).fill('#f06');
var rect = draw.rect(100, 100).attr({fill: '#eee'}).dx(200).dy(200);
var circle = draw.circle(100).attr({fill: '#ff0'}).dx(300).dy(300);
$('#button').on('click', function () {
var svgTag = $('svg').get(0).outerHTML;
var canvas = $('<canvas>').get(0);
canvg(canvas, svgTag, {
renderCallback: function() {
$('body').find('.convImg').remove();
$('body')
.append($('<img>')
.css('border', 'solid 1px')
.addClass('convImg')
.attr('src', canvas.toDataURL()));
},
});
});
</script>
</body>
ただし、これを実行してみると分かりますが、残念ながら画像が粗くなってしまいます。
canvgのドキュメントを見てみても、クオリティやスムージングといったキーワードが見つからず、こんなものなのかと嘆いています。
このようなsvgタグが
画像化されました。が、粗い。。。フォントもなんかジャギってる。つーか、文字自体が小さくなってる。。。
ちなみにこのコード、Chromeで動作確認を取りましたが、IEでは動いてくれませんでした。色々調べてみるとIEでは一工夫必要なようですので、ぜひ調べてみてください。
で、画像の粗さについて、もうちょっと改善する方法がないものか調べてみたところ、canvgにはcanvasの「drawImage」と似た「drawSvg」という関数が準備されていることが分かりました。
canvasのgetContextの際に、「imageSmoothingEnabled」というパラメータが渡せるので、これで少しは違うかもしれないと思い試してみたところ。。。残念ながら結果は変わらず。(どうやらまだexperimentalな仕様のようですね)
他にもsvgの「shape-rendering」やcanvasの「image-rendering」といったパラメータがあるので、この辺を組み合わせるともしかしたら改善するかもしれないです。が、今回は未調査。
<body>
<svg id="drawing" style="border:solid 1px;"></svg>
<div><button id="button">convert to image</button></div>
<script>
var draw = SVG('drawing').size(500, 500);
var image = draw.image('images/NKJ56_gatsudanshi500.jpg', 150, 100).dx(30).dy(30);
var text = draw.text('SVGを画像化できますか?').move(150, 150).fill('#f06');
var rect = draw.rect(100, 100).attr({fill: '#eee'}).dx(200).dy(200);
var circle = draw.circle(100).attr({fill: '#ff0'}).dx(300).dy(300);
$('#button').on('click', function () {
var svgTag = $('svg').get(0).outerHTML;
var $canvas = $('<canvas>').attr('width', '500px').attr('height', '500px').addClass('convCanvas');
var context = $canvas.get(0).getContext('2d');
context.imageSmoothingEnabled = true;
context.drawSvg(svgTag, 0, 0);
$('body').find('.convCanvas').remove();
$('body').append($canvas);
});
</script>
</body>
-
今回の検証に際し利用したもの
- Yeomanジェネレータのgenerator-webapp(検証用HTMLのひな形作成)
- Bower(svg.jsとcanvgのインストール)
- PAKUTASOのつよぽん画像([ photo by pakutaso.com ])
-
参考URL