Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
30
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

canvasを画像としてダウンロードさせたかった話

Qiita初投稿につき不作法等あるかと思うので、ご指摘いただけると幸いです。
プログラミングぢからも底辺なため、そちらも合わせてご指導願います。

やりたかったこと

  1. ユーザがWebページ上のボタンをクリックする
  2. <canvas>の描画内容がpng画像に変換される
  3. 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も含めて)常に表示しとく方が良いなと思い直した。

というわけで最終形

hoge.html
<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>
hoge.js
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上にデプロイしてからテストすれば何の問題もなく動いた。結構時間食って悲しかったので一応書き添えておきます。根本的な解決方法は知らぬ……。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
30
Help us understand the problem. What are the problem?