HTMLを画像に変換する「html2canvas.js」の変換速度を高速化する[Base64/リロード対策]
JavaScriptでHTMLを画像に変換するオープンソースの「html2canvas.js」ですが、何も対策を施さないまま使用するとHTML内にある元の画像が「変換」するたびにリロードされる設定になっています。また、変換対象のHTMLにある画像がBase64形式の場合はアスキーですので変換速度が極端に落ちます。
今回はこれらを対策する事によって変換速度を向上する方法をご紹介します。
HTMLから画像に変換する
はじめての方は公式サイトで変換のテストをすることができます。
https://html2canvas.hertzen.com/ (英語)
※IEにはPromiseオブジェクトがありませんのでPolyfillライブラリの「native-promise-only」の「npo.src.js」もダウンロードします。(代替品でも可)
html2canvas.jsの使い方
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="npo.src.js"></script> <script src="html2canvas.js"></script> </head> <body> <div id="dummy" style="color:green;">プチモンテ</div> <p>この下にあるのが画像です。</p> <script> var dmy = document.getElementById("dummy"); html2canvas(dmy).then(function(canvas){ document.body.appendChild(canvas); }).catch(function(err){ alert(err); }); </script> </body> </html>
[実行結果]
14行目で変換を実行します、成功すると15行目、失敗すると17行目が実行されます。「then」「catch」はPromiseオブジェクトの機能です。
Base64の対策
変換対象のHTML内にBase64形式の画像があると、変換速度が極端に遅くなります。回避策としては画像をwindow.URL.createObjectURLで「オブジェクトURL」にする方法があります。
[画像をオブジェクトURLに変換する]
var url = URL.createObjectURL(new Blob([buffer]));
bufferに「ArrayBuffer」や「Uint8Array」の画像データを指定します。戻り値のURLがオブジェクトURLで「blob:」形式の文字列となります。このURLを変換対象のHTMLのimg.srcに埋め込みます。
ただ、問題なのが「html2canvas」では「blob:」形式には未対応ですので、対応できるように「html2canvas」のソースコードを修正します。修正する箇所は「ImageLoader.prototype.loadImage = function(imageData) {}」となります。
[html2canvas.js 0.5.0-beta3の修正]
ImageLoader.prototype.loadImage = function(imageData) { if (imageData.method === "url") { var src = imageData.args[0]; if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) { return new SVGContainer(src); } else if (src.match(/data:image\/.*;base64,/i)) { return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false); // *************************************************** // 追記箇所 // *************************************************** } else if (src.match(/blob:/i)) { var blobimg = new ImageContainer(src, false); // オブジェクトURLを削除する URL.revokeObjectURL(src); return blobimg; // *************************************************** } else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) { return new ImageContainer(src, false); } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) { return new ImageContainer(src, true); } else if (this.options.proxy) { return new ProxyImageContainer(src, this.options.proxy); } else { return new DummyImageContainer(src,false); } } else if (imageData.method === "linear-gradient") { return new LinearGradientContainer(imageData); } else if (imageData.method === "gradient") { return new WebkitGradientContainer(imageData); } else if (imageData.method === "svg") { return new SVGNodeContainer(imageData.args[0], this.support.svg); } else if (imageData.method === "IFRAME") { return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options); } else { return new DummyImageContainer(imageData); } };
修正といってもコードを追記しているだけです。次はリロード対策です。
リロード対策
html2canvas.jsは画像に変換する際に、html2canvas.jsが読み込まれているHTML全体をリロードします。特に問題となるのが画像です。変換とは無関係な画像が大量にあると無駄に変換時間がかかってしまいます。
次のコードはHTMLを3回、画像に変換します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="npo.src.js"></script> <script src="html2canvas.js"></script> <script> var index = 0; // HTMLを画像に変換する x3 function OnClick(){ var dmy = document.getElementById("dummy"); dmy.innerHTML = 'プチモンテ' + (index+1); html2canvas(dmy).then(function(canvas){ document.body.appendChild(canvas); index++; if(index === 3){ index = 0; }else { OnClick(); } }).catch(function(err){ alert(err); }); } </script> </head> <body> <img src="none.jpg"> <div id="dummy" style="color:green;"></div> <button onclick="OnClick();">実行</button> <p>この下にあるのが画像です。</p> </body> </html>
[実行結果]
最初のエラーは「none.jpg」が無いよというHTMLのエラーです。残りの3回はhtml2canvas.jsがリロードして発生した3回のエラーです。
※html2canvas.jsのソースコードを見る限りでは、SCRIPTタグはリロードしないようです。その他の詳細はソースをみると早いです。
回避策
この画像のリロードを回避するにはメインページとは別に「変換するページを作成」して「iframe」タグで読み込むようにすれば良いです。
[main.html]
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> iframe{ border:none; margin:0; padding:0; height:350px; width:350px; } </style> </head> <body> <img src="none.jpg"> <p></p> <iframe src="frame.html" style="" scrolling="no" frameborder="no"></iframe> </body> </html>
[frame.html]
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="npo.src.js"></script> <script src="html2canvas.js"></script> <script> var index = 0; // HTMLを画像に変換する x3 function OnClick(){ var dmy = document.getElementById("dummy"); dmy.innerHTML = 'プチモンテ' + (index+1); html2canvas(dmy).then(function(canvas){ document.body.appendChild(canvas); index++; if(index === 3){ index = 0; }else { OnClick(); } }).catch(function(err){ alert(err); }); } </script> </head> <body> <div id="dummy" style="color:green;"></div> <button onclick="OnClick();">実行</button> <p>この下にあるのが画像です。</p> </body> </html>
[実行結果]
表示されているエラーはHTML側でのエラーだけとなりましたね。
iframeのエリアはCSSで枠を消していますので、実際2ページあるのが見た目は1ページに見えますね。