こんにちは。きんくまです。
今回は、「動的SVGからpngを作りたいときはcanvgを使う。Image.src経由だとIEのSecurity Errorが出ちゃう」です。(長い)
SVGを動的にJavaScriptでいろいろいじったものを画像化したかったのですが、最初にやってた方法だとIE11でエラーが出てしまいました。
2018年3月の現時点だと、SVGから直接png化するメソッドはなさそうです。
なので、SVGをcanvasに描画して、そこからtoDataURLでbase64化するという流れになります。
でハマった方を書く前に忙しい方のために、先にcanvgを使った方法を書いておきます。
canvgでsvgを画像化する
>> GitHub – canvg/canvg: Javascript SVG parser and renderer on Canvas
canvgはSVGをパースして、Canvasの中に再現するライブラリです。
サンプルhtml(中身てきとうです)
JSで最終的にこんな感じのSVGができたとします。
<input type="button" value="save" id="saveButton" /><br> <svg id="mysvg" version="1.1" xmlns="http://www.w3.org/2000/svg" width="400" height="400" > <rect width="100%" height="100%" fill="red"></rect> <circle cx="150" cy="100" r="80" fill="green"></circle> <text x="150" y="125" font-size="32" text-anchor="middle" fill="white">えすぶいじー</text> <circle cx="250" cy="300" r="80" fill="blue"></circle> </svg>
参考)キャプチャ
JSのcanvgを使う部分。
動的にcanvasを生成して、canvgを使ってcanvasにSVGを再現。toDataURLを使って、base64化されたpngイメージのデータ(文字列)を取得します。
var OFF_SCREEN_CANVAS_ID = "svgOffScreeenRenderCanvas"; var OFF_SCREEN_CANVAS_CLASS = "svg-off-screen-render-canvas"; function saveToPngByCanvg(svgId, callback){ var svg = document.getElementById(svgId); var svgStr = new XMLSerializer().serializeToString(svg); var canvas = document.getElementById(OFF_SCREEN_CANVAS_ID); if(!canvas){ var svgW = svg.getAttribute('width'); var svgH = svg.getAttribute('height'); canvas = createOffScreenCanvas(svgW, svgH); } canvg(OFF_SCREEN_CANVAS_ID, svgStr, { renderCallback:function(data){ if(callback){ var data = canvas.toDataURL('image/png'); callback(data); } } }); } function createOffScreenCanvas(width, height){ var newCanvas = document.createElement('canvas'); newCanvas.setAttribute('id', OFF_SCREEN_CANVAS_ID); newCanvas.setAttribute('width', width); newCanvas.setAttribute('height', height); //styleの設定 var style = newCanvas.style; style.position = 'absolute'; style.left = '-9999px'; style.top = '0px'; newCanvas.classList.add(OFF_SCREEN_CANVAS_CLASS); document.querySelector('body').appendChild(newCanvas); return newCanvas; }
使い方
window.onload = function(){ document.getElementById('saveButton').addEventListener('click', function(){ saveToPngByCanvg('mysvg', function(data){ console.log('callback----'); console.log(data); //ここからajaxでもfetchでも使ってサーバーにデータを送る }); }) };
画像データを保存したいサンプル
canvasでbase64のテキストを取得したらサーバーに保存します。
jQueryで通信してますけど、気になる人はfetchでも良いです。
JavaScript
function postImageData(dataURL){ $.ajax({ type:'post', url: './save_image.php', data: { imgBase64: dataURL //ここに連携するパラメータを追加 ,id: '12345abc' } }).done(function(msg){ console.log(msg); }) }
サーバー側
save_image.php
<?php // requires php5 // 保存するフォルダ名 writeの所有者権限を追加する必要があります define('UPLOAD_DIR', 'images/'); $id = $_POST['id']; //base64 / urlencoded された画像データ(文字列) $img = $_POST['imgBase64']; $img = str_replace('data:image/png;base64,', '', $img); $img = str_replace(' ', '+', $img); $data = base64_decode($img); $file = UPLOAD_DIR . $id .'.png'; //$file = UPLOAD_DIR . uniqid() . '_'. $id .'.png'; $success = file_put_contents($file, $data); //クライアントにメッセージを返す print $success ? '画像を保存しました'.$file : '画像が保存中にエラーが発生しました';
気をつけるポイント。SVGを設定するときはclassではなくインラインでスタイルを設定する
SVGの中のrectやgなど各エレメントはcssのclassでスタイルを設定することが可能です。(外部cssファイルとかに書ける)
ただし動的に画像化するときは、classで設定することはできません。
手順を考えてみるとわかるのですが、SVGをシリアライズした際にそこにはclassから計算されたスタイルは読み込まれません。
なので、インラインで全て書いてあげる必要があります。(ていうかここでもハマった)
Image経由でcanvasにレンダリングするとIEでセキュリティエラーが出る
というわけで本編です。
手順
1. SVGからDOMをシリアライズ
2. 新規のImageのsrcに1を入れる
3. Imageのonloadを待つ
4. Canvasに2をdrawImage
5. CanvasのtoDataURLでbase64化する
IEはどこでエラーを出すかというと5です。
どうやら調べたところ、IEは外部のドメインのImageが入っているCanvasのtoDataURLを呼んだときにセキュリティエラーを出す仕様のようです。
>> IE throws Security Error when calling toDataUrl on canvas
書いたJS
function svgToPngByNative(svgId, callback){ var svg = document.getElementById(svgId); var svgStr = new XMLSerializer().serializeToString(svg); svgStr = encodeURIComponent(svgStr); var newImage = new Image(); newImage.crossOrigin = "Anonymous"; newImage.onload = function(){ var canvas = document.getElementById(OFF_SCREEN_CANVAS_ID); if(!canvas){ const svgW = svg.getAttribute('width'); const svgH = svg.getAttribute('height'); canvas = createOffScreenCanvas(svgW, svgH); } var ctx = canvas.getContext("2d"); ctx.drawImage(newImage, 0, 0); if(callback){ //ここでIEのセキュリティエラーが起きた const data = canvas.toDataURL('image/png'); callback(data); } }; //document.querySelector('body').appendChild(newImage); newImage.src = "data:image/svg+xml," + svgStr; }
じゃあなんでcanvgはセキュリティエラーが出ないかというと、canvasで1から動的にちゃんとデータを作っているからだと思います。
data:image の Imageを drawImageしているわけじゃないから。
以前やったことある案件でCanvasを1から動的に生成したあと、toDataURLしたときもエラーが出てなかったです。
将来的にSVGから直接pngに書き出すことができると良いですね。
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ