Qiita初投稿につき不作法等あるかと思うので、ご指摘いただけると幸いです。
プログラミングぢからも底辺なため、そちらも合わせてご指導願います。
やりたかったこと
- ユーザがWebページ上のボタンをクリックする
-
<canvas>
の描画内容がpng画像に変換される - png画像が自動でダウンロードされる
たった3行の要件に結構な時間かかった。つらい。
どのように困ったのか
①「.click()」がFirefoxで動作しない
【問題】
最初に書いたJavaScriptはこんな感じ。仮想の<a>
要素を作ってからURLを仕込んでクリックさせればええんちゃう?といった感じ。
let canvas = document.getElementById('targetCanvas')
let link = document.createElement('a')
link.href = canvas.toDataURL()
link.download = 'cancas.png'
link.click()
とりあえず自分のメインブラウザたるChromeで検証したら、あっさり意図したとおりに動いた。いいね!
しかし次にFirefoxで検証したところ、ピクリとも動かず。よくないね!
【原因】
Firefoxでは実在しない要素に対して.click()
できない模様。まあ気持ちは分かる。
【対策】
A案:appendChild()
で要素を追加してから.click()
する。
B案:予めHTML内に<a id="hoge">
を書いておき、JS側でhref
属性を書き換える。
今回は実装が簡単そうなB案を採用。<a id="hoge">
がユーザに見えないようCSSでdisplay: none;
したら、見た目も動作も当初案と同じになって無事解決した。
②それでも「.click()」がIE/Edgeで動作しない
【問題】
なんとなく「IE/Edgeはすんなり動いてくれないだろうなあ」と思ってたけど、案の定ノーリアクション。もしやと思ってdisplay: none;
外して手動クリックしてみたら、ページ遷移のエラーが発生した。
【原因】
今回のlink.href = canvas.toDataURL()
の部分は、いわゆる「https://~」の形式ではなくて、「data:image/png;base64,とても長い文字列~」となる。「データURIスキーム」って言うんだって、知らんかった。
なおこれについてWikipediaで調べてみると……
2018年現在、データURIは主要なほとんどのブラウザで完全にサポートされている。ただし、Internet ExplorerとMicrosoft Edgeでは、一部の機能が実装されていない。
あっはいそうなんすね。マイクロソフトさんさすがっす。
【対策】
当初要件の「png画像を自動でダウンロードさせる」は、IE/Edgeでは実装困難と判断して諦めることに。画像をサーバ側に一回保存して、改めて<a href="https://hoge.com/image.png">
とかやれば不可能ではないんだろうけど、そこまでの技術力は無い流石にめんどい。
代用として、ダウンロードさせたかった画像をページ上に小さく表示させ、手動で「右クリック→名前を付けて保存」してもらうことにした。我ながらダサい……もっとスマートなアイディアあったら誰か教えてください。
ちなみにIE/Edgeのときだけ当該部分が表示されるようにCSS弄ってみたりもしたけど、有象無象のスマホブラウザ共が自動ダウンロードできるのかとか、IE/Edge専用CSSがちゃんと反映されるのかとか、色々考えたら(正常動作するChromeとFirefoxも含めて)常に表示しとく方が良いなと思い直した。
というわけで最終形
<body>
<canvas id="targetCanvas"></canvas>
<button onclick="downloadCanvas()">図としてダウンロード</button>
<a id="hiddenLink" download="canvas.png">link</a>
<!-- CSSで「display: none;」して非表示 -->
<p>自動でダウンロードされない場合、下図を右クリックして保存してください。</p>
<img id="canvasImage" src="dummy.png">
<!-- CSSで小さめサイズに調整 -->
</body>
function downloadCanvas() {
let canvas = document.getElementById('targetCanvas')
let link = document.getElementById('hiddenLink')
link.href = canvas.toDataURL()
document.getElementById('canvasImage').src = canvas.toDataURL()
link.click()
}
これでChromeとFirefoxでは要件通りに動作、IE/Edgeでも手動スクショ強要はせずに済んだ。めでたしめでたし。
いやホントはSafariとかスマホ版Chromeとかでも検証しないといけないんだけどね。おいおいやります。
番外編 ~.toDataURL()がエラー吐く話~
ローカルでテストしてたとき、画像ファイルを<canvas>
要素に取り込んでから.toDataURL()
すると、Chromeが下記のエラーを吐いた。
DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
汚染されたキャンバスはエクスポートできません?なるほどわからん。
ググったらこんな記事が見つかりました。要約すると「外部コンテンツを取り込んだcanvasはセキュリティの都合でExportできないよ」ってことかな。ええ、いや、自サイト内の画像を取り込んでるだけなんですけど。
結局このエラーが出るのはローカル上で動かしてるときだけで、Web上にデプロイしてからテストすれば何の問題もなく動いた。結構時間食って悲しかったので一応書き添えておきます。根本的な解決方法は知らぬ……。