こんにちは。きんくまです。
今回は、「動的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ができたとします。
1 2 3 4 5 6 7 8 9 10 | < input type = "button" value = "save" id = "saveButton" />< br > < svg id = "mysvg" 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イメージのデータ(文字列)を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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; } |
使い方
1 2 3 4 5 6 7 8 9 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 | function postImageData(dataURL){ $.ajax({ type: 'post' , url: './save_image.php' , data: { imgBase64: dataURL //ここに連携するパラメータを追加 ,id: '12345abc' } }).done( function (msg){ console.log(msg); }) } |
サーバー側
save_image.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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スタンプ作りました!
毎日使える。とぼけたウサギ