ファイルとして保存

 canvas要素で作った画像、せっかく作ったのでローカルなストレージにファイルとして保存したい事もあります。 ファイルになれば後から処理を加えることもできます。それに作った画像をSNSで共有させたいですよね。

 Chromeの場合、canvas要素を右クリックすると画像として保存できます。 Edge/IEの場合、canvas要素を右クリックしてもダウンロードできません。他のブラウザではどうでしょうか? いずれにしろそのcanvas要素をそのままダウンロードできないブラウザがあるので、 今回はそれらにも対応した方法で保存できるようにします。

Canvas要素を載せたHTML5の書き方

 htmlファイルを用意します。 文字エンコードは「UTF-8(BOM無し)」で保存してください。(サンプルページが下にあります。)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>canvas sample</title>
    <style>
      #canvas, #newImg {
          background: #666;
          border: 1px solid black;
      }
    </style>
  </head>
  <body>
    画像を右クリックして「名前をつけて保存」
    <div><canvas id="canvas" width="320" height="240"></canvas></div>
    <div><img id="newImg"></div>
    <a href="" id="dlImg">ダウンロード(IE除く)</a>
    <button id="btnDownload">ダウンロード</button>
    <div style="height:100vh;"></div>
    <script src="sample04.js"></script>
  </body>
</html>

 height:100vh;の付いたdivタグは、スマホで見た時のオーバーラップ広告対策なので気にしないでください。ここのサイトの場合、こうやって広げとかないとcanvasやボタンの上に広告が重なってしまって操作できなくなってしまうので。

 空のimg要素と空のa要素、button要素を設置しています。 この3種の要素それぞれで、作った画像をダウンロードさせてみようと思います。

動くサンプル

// canvas要素
var cve = document.getElementById("canvas");

// --------------------
//  メイン
// --------------------
if (cve.getContext) {
	var ctx = cve.getContext('2d');

	// --------------------
	//  図形を描画
	// --------------------
	// canvas に図形を描く
	// https://developer.mozilla.org/ja/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes
	ctx.fillStyle = 'white';
	ctx.fillRect(0, 0, cve.width, cve.height);
	ctx.beginPath();
	ctx.arc(75,75,50,0,Math.PI*2,true); // 外の円
	ctx.moveTo(110,75);
	ctx.arc(75,75,35,0,Math.PI,false);  // 口 (時計回り)
	ctx.moveTo(65,65);
	ctx.arc(60,65,5,0,Math.PI*2,true);  // 左目
	ctx.moveTo(95,65);
	ctx.arc(90,65,5,0,Math.PI*2,true);  // 右目
	ctx.stroke();

	// canvaasを非表示にする
	// 図形描画前でもよい
	cve.hidden = true;

	// --------------------
	//  imgタグに表示
	// --------------------
	// canvasをBase64データに変換
	var base64 = cve.toDataURL();
	// 空のimgタグにcanvasで作成した画像を載せる。
	// 以降は通常の画像と同じなので右クリックから保存できるようになる。
	document.getElementById("newImg").src = base64;

	// --------------------
	//  ダウンロード
	// --------------------
	// ダウンロード ファイル名
	var fileName   = "sample.png";
	// base64データをblobに変換
	var blob = Base64toBlob(base64);

	//  ダウンロード リンクを設定
	// Edge, Chrome等はこれでaタグからダウンロードできるようになります。
	// IEではこの方法ではaタグからダウンロードできません。
	
	// BlobをBlob URL Schemeへ変換
	document.getElementById("dlImg").href = window.URL.createObjectURL(blob);
	// ダウンロードファイル名を設定
	document.getElementById("dlImg").download = fileName;
}

// --------------------
//	イベントのリスナーを登録
// --------------------
//	ダウンロードボタン
document.getElementById("btnDownload").addEventListener("click", DownloadStart, false);


// --------------------
//  ダウンロード開始
// --------------------
function DownloadStart(){
	if (cve.getContext) {
		var ctx = cve.getContext('2d');

		// ダウンロード ファイル名
		var fileName   = "sample.png";

		//  ダウンロード開始
		if (window.navigator.msSaveBlob) {
			// IE
			// base64データをblobに変換してダウンロード
			window.navigator.msSaveBlob(Base64toBlob(base64), fileName);
		} else {
			// Chrome, Firefox, Edge
			// aタグをクリックしてダウンロード開始
			document.getElementById("dlImg").click();
		}
	}
}

// --------------------
// Base64データをBlobデータに変換
// --------------------
// https://st40.xyz/one-run/article/133/
function Base64toBlob(base64)
{
	var tmp = base64.split(',');
	var data = atob(tmp[1]);
	var mime = tmp[0].split(':')[1].split(';')[0];
	var buf = new Uint8Array(data.length);
	for (var i = 0; i < data.length; i++) {
		buf[i] = data.charCodeAt(i);
	}
	var blob = new Blob([buf], { type: mime });
	return blob;
}

canvasに図形を描画。
img要素とa要素、button要素に作った画像データを反映させています。
個別に見ていきましょう。
サンプルページ

解説:img要素

 用意したcanvasに図形を描いたら、あとは今回は使わないのでcve.hidden = true;としてcanvas要素を非表示にします。

var base64 = cve.toDataURL();
document.getElementById("newImg").src = base64;

 canvasをBase64データの文字列(「data:」で始まる文字列)に変換。これをimgタグのsrc要素に突っ込みます。 これだけでimgタグに画像が表示されます。

 表示さえされてしまえば、あとはほとんど普通の画像をimgタグで表示しているのと変わりがありません。 画像を右クリックすればダウンロードできますし、スマホなら長押しすれば「画像を共有」からツイートできると思います。(ブラウザやバージョンによってはできないものもあるかもしれませんが。)

 私としては、3つの中でこれが一番おすすめの方法です。 img要素に表示するだけなのでブラウザの対応も十分ですし、なにより実装が簡単です。 ユーザーに画像をダウンロードさせる手間を取らせずにSNSで共有出来るのも、この方法の良いところでもあります。

解説:a要素

Base64データの文字列になっている画像データを、blobに変換します。

var blob = Base64toBlob(base64);

 Base64toBlob関数は、こちらのサイトに書かれたものを使用しました。詳しい情報については下記サイトを御覧ください。
Canvas上のイメージを画像ファイルとして保存する方法 - ONE-RUN
https://st40.xyz/one-run/article/133/

 本来は、toBlob()メソッド というBlob変換できるメソッドが仕様上存在しているのですが、ブラウザの対応状況がまだ十分ではありません。
この記事を書いている2018年4月時点では、主なブラウザではChrome(50以降)とFirefox(25以降)が対応。 Edge,IE11ではmsToBlob()という独自のメソッドで実装。 その他のブラウザでは未サポートのようです。もう少し環境が整うまではBlobへの変換は自前で実装したほうが良さそうです。

 画像をBlobに変換したら、Blob URL Scheme(「blob:」で始まる文字列)に変換します。 これを、a要素のhrefプロパティに入れるとダウンロードでるリンクになります。

document.getElementById("dlImg").href = window.URL.createObjectURL(blob);

 ついでなのでdownloadプロパティで、ダウンロード ファイル名も指定します。

document.getElementById("dlImg").download = fileName;

 これで、a要素をクリックするとcanvasの画像をpng形式のファイルとしてダウンロードすることが出来るようになります。 ただしこの方法だとIEが対応していません。IEの場合、この方法で作ったリンクをクリックすると ダウンロードではなくアプリケーションで開こうとします。一体何がしたいのでしょうIEは。この対策は後述します。

 これでa要素の方法については終わりですが、toBlobの話が出たのでtoBlob()/msToBlob()を使用したスクリプトも書いてみます。

// --------------------
//  ダウンロード
// --------------------
// ダウンロード ファイル名
var fileName   = "sample.png";
// base64データをblobに変換
if (cve.msToBlob) {
	// Edge
	var blob = cve.msToBlob()
	// BlobをBlob URL Schemeへ変換
	document.getElementById("dlImg").href = window.URL.createObjectURL(blob);
	// ダウンロードファイル名を設定
	document.getElementById("dlImg").download = fileName;
} else if (cve.toBlob) {
	// Firefox, Chrome
	cve.toBlob(function(blob) {
		//  ダウンロード リンクを設定
		// Edge, Chromeはこれでaタグからダウンロードできるようになります。
		// IEではこの方法ではaタグからダウンロードできません。
		
		// BlobをBlob URL Schemeへ変換
		document.getElementById("dlImg").href = window.URL.createObjectURL(blob);
		// ダウンロードファイル名を設定
		document.getElementById("dlImg").download = fileName;
	});
} else {
	alert("Blob変換できませんでした。");
}

 結局対応していないブラウザのための対応が必要なのであまり意味がないですね。

解説:button要素

 button要素で使用している方法は、a要素で使用している方法と途中まで同じです。 まずBlobに変換してa要素にリンクを作ります。 あとはこの要素でclick()メソッドを実行するとダウンロードさせることができます。 実質a要素の項目で説明した方法そのものですが、これでは先ほど説明したとおりIEではダウンロードできません。 IEの場合は、msSaveBlob()メソッドを使用します。

window.navigator.msSaveBlob(Base64toBlob(base64), fileName);

 これでIEでもダウンロードできるようになりました。 しかしブラウザの対応状況がまちまちなので、クロスブラウザを考慮するとなかなか面倒ですね。 今更ながら、対応してくれてるライブラリ探したほうが早かったんじゃないかと思いました。

おわり

 今回で予定していたcanvas再入門は終わりです。 ネタでちょっとした画像加工ツールを作るにはこれで十分じゃないでしょうか。 もっといろいろやりたい場合は、canvas向けのライブラリを探すといいと思います。