<canvas> 上で描画・編集した内容を画像として書き出す際、JavaScriptには主に canvas.toDataURL() と canvas.toBlob() の2つのメソッドが用意されています。
どちらもCanvasの内容を画像化できますが、「画像のダウンロード処理やファイル保存でなぜ toBlob() が推奨されるのか」「それぞれの使い分けはどう判断すればいいのか」で迷うケースは少なくありません。
この記事では、toDataURL() と toBlob() の根本的な違いから、toBlob() を用いた安全な画像ダウンロードの実装パターン、画質設定(quality)の仕様、エラー(SecurityError)の切り分けポイントまでを解説します。
Canvas自体の表示がぼやける場合は、描画サイズとdevicePixelRatioの調整方法もあわせて確認してください。
canvasの画像を書き出す2つの方法
まずはそれぞれの基本的な使い方と取得できるデータ形式を確認します。
toDataURL()は画像をData URLとして取得する
toDataURL() は、Canvasの描画内容を Base64形式のData URL(文字列) として同期的に取得するメソッドです。
const dataURL = canvas.toDataURL();
console.log(dataURL);
// 出力結果: "data:image/png;base64,iVBORw0KGgoAAA..."
返り値がそのままURL文字列になるため、<img src="..."> に直接代入して即座にプレビュー表示させたい場合などに手軽に使えます。
toBlob()は画像をBlobとして取得する
toBlob() は、Canvasの描画内容を バイナリデータである Blob オブジェクト として非同期(コールバック関数)で取得するメソッドです。
canvas.toBlob((blob) => {
console.log(blob);
// 出力結果: Blob { size: 12345, type: "image/png" }
});
文字列ではなくバイナリデータのまま扱えるため、ファイルの保存・ダウンロード処理や、サーバーへのアップロード(FormData / fetch)に適しています。
toDataURL()とtoBlob()は何が違う?
2つのメソッドの主な特徴を比較すると以下のようになります。
| 比較項目 | canvas.toDataURL() |
canvas.toBlob() |
|---|---|---|
| 取得できるデータ | Data URL(Base64エンコード文字列) | Blob オブジェクト(バイナリデータ) |
| 処理方式 | 同期処理(即座に文字列が返る) | 非同期処理(コールバック関数で受け取る) |
| 大きな画像の扱い | 文字列化による負荷やメモリ消費に注意 | Blobのまま扱えるため扱いやすい |
| 画面表示 | そのまま img.src に代入可能 |
URL.createObjectURL() で一時URLを発行して表示 |
| ファイルダウンロード | 可能(href に長い文字列を指定) |
向いている(Object URLを発行してダウンロード) |
大きな画像ではtoBlob()を使う方が扱いやすい
実務で画像保存やファイル出力を扱う際に toBlob() が選ばれる最大の理由は、データサイズと処理負荷の違い にあります。
toDataURL()による文字列化の注意点
toDataURL() は画像データをBase64形式の文字列へ変換します。
Base64で表現した文字列は、元のバイナリデータに対しておおむね4/3程度の長さになり、さらにData URLのヘッダーも加わります。
大きなCanvasでは、長いJavaScript文字列を同期的に生成・保持することになり、パフォーマンスやメモリ使用量への影響が大きくなります。
// 大きなCanvasでは巨大な文字列が生成される
const dataURL = canvas.toDataURL('image/png');
toBlob()ならバイナリのまま効率的に扱える
一方、toBlob() はデータを文字列に変換せず、バイナリ(Blob)のままブラウザ内部で生成されます。
canvas.toBlob((blob) => {
// Blobのまま効率的に処理できる
});
「toDataURL() は使ってはいけない」というわけではありません。
- アイコンや小さなプレビューを即座に表示したい →
toDataURL() - ユーザーに画像を保存・ダウンロードさせたい、大きめのCanvasを扱う →
toBlob()
という基準で使い分けるのが実務的です。
toBlob()でCanvas画像をダウンロードする
Canvasの内容を画像ファイルとしてユーザーのPC・端末へ保存させる実務的な定石コードです。
const canvas = document.querySelector('#canvas');
const downloadBtn = document.querySelector('#download-btn');
downloadBtn.addEventListener('click', () => {
canvas.toBlob((blob) => {
// 1. 生成に失敗した場合のガード
if (!blob) return;
// 2. Blobから一時的なObject URLを生成
const url = URL.createObjectURL(blob);
// 3. ダウンロード用のa要素を生成してクリック
const link = document.createElement('a');
link.href = url;
link.download = 'canvas-image.png';
link.click();
// 4. 不要になったObject URLを解放
URL.revokeObjectURL(url);
});
});
Blob を受け取った後に URL.createObjectURL(blob) で一時URLを発行し、a[download] をクリックさせてから URL.revokeObjectURL(url) で解放します。
※ ダウンロードが開始された後に revokeObjectURL() を呼ぶことで、メモリ上に不要なObject URLを残さずに済みます。
PNG・JPEG・WebPを指定する方法
toBlob()は第2引数にMIMEタイプ、第3引数にqualityを指定します。
toDataURL()は第1引数にMIMEタイプ、第2引数にqualityを指定します。
// JPEG形式・quality 0.8で書き出し
canvas.toBlob(
(blob) => {
console.log(blob); // type: "image/jpeg"
},
'image/jpeg',
0.8
);
// WebP形式・quality 0.9で書き出し
canvas.toBlob(
(blob) => {
console.log(blob); // type: "image/webp"
},
'image/webp',
0.9
);
- MIMEタイプを省略した場合:デフォルトで
image/png(PNG形式)として処理されます。ブラウザが対応していない形式を指定した場合もPNGへフォールバックします。 quality引数:0 から 1 までの数値で、対応する画像形式の出力品質を指定します。
qualityを指定してもPNGの容量は変わらない?
品質(quality)引数は、JPEGやWebPのように 可変品質をサポートしている形式でのみ有効 です。
// PNGに 0.5 を指定しても品質指定による圧縮は行われない
canvas.toBlob(callback, 'image/png', 0.5);
可逆圧縮であるPNGに対して quality に数値を渡しても、画質圧縮による容量調整は行われません。
quality 引数を使って出力品質や容量を調整したい場合は、JPEGやWebPなど、可変品質をサポートする形式を使用します。
toDataURL()とtoBlob()による画像表示の比較
toDataURL()で画像を表示する場合
同期的に返る文字列をそのまま src に渡します。
const preview = document.querySelector('#preview');
preview.src = canvas.toDataURL('image/png');
コード量が少なくシンプルですが、大きな画像ではBase64文字列の扱いに配慮が必要です。
toBlob()で画像を表示する場合
コールバック内で URL.createObjectURL() を使って表示します。
const preview = document.querySelector('#preview');
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
preview.src = url;
});
コードは非同期になりますが、巨大なData URL文字列を生成せず、Blobのまま扱ってプレビューできます(※差し替え時には直前のObject URLを適切に revokeObjectURL() で解放する運用が推奨されます)。
Object URLの生成・解放タイミングについては、こちらの記事で解説しています。
toDataURL()やtoBlob()がエラーになる原因
画像書き出し処理を実行した際にエラーが出る場合、以下の原因を確認してください。
1. 外部画像をCanvasへ描画して「汚染(tainted)」されている
別オリジンの画像(異なるドメインから配信される画像など)で適切なCORSヘッダーがないものを ctx.drawImage() でCanvasに描画した場合、ブラウザのセキュリティ仕様によりCanvasが「origin-clean」でなくなります(汚染状態)。
この状態で toDataURL() や toBlob() を呼ぶと SecurityError が発生して画像を取得できません。
外部画像を描画する場合は、画像配信元サーバー側で適切なCORSヘッダー(Access-Control-Allow-Origin)を返していることを前提に、JavaScript側で src を設定する前に crossOrigin 属性を指定して読み込む必要があります。
const img = new Image();
// ※必ず src を設定する前に crossOrigin を指定する
img.crossOrigin = 'anonymous';
img.src = 'https://example.com/image.jpg';
img.onload = () => {
ctx.drawImage(img, 0, 0);
// CORSが適切に設定されていれば toBlob / toDataURL が可能
};
2. Canvasのwidthまたはheightが0になっている
HTML側やJS側で canvas.width = 0 または canvas.height = 0 の状態になっていると、toBlob() では null が返り、toDataURL() では "data:," という空データが返ります。
3. toBlob()のblobがnullになっていないか
Canvasのメモリ不足やサイズ異常などにより、toBlob() のコールバックに渡される blob が null になるケースがあります。
必ず if (!blob) return; のガード節を入れて処理を中断できるようにしておきます。
toDataURL()とtoBlob()の使い分け
実務での判断基準を整理すると以下のようになります。
| 実装したい要件・用途 | 推奨するメソッド |
|---|---|
小さなCanvasの描画結果を即座に img.src へ入れたい |
toDataURL() |
| ユーザーにCanvas画像をファイルとしてダウンロードさせたい | toBlob() |
| 大きめの解像度のCanvasを書き出す | toBlob() |
| Data URL文字列そのものが必要(HTML/CSS埋め込み等) | toDataURL() |
生成した画像をサーバーへ送信・API通信へ渡す(FormData 等) |
toBlob() |
まとめ
Canvasの画像書き出しは、用途とデータサイズに応じて toDataURL() と toBlob() を適切に使い分けます。
toDataURL():手軽にData URL文字列を取得できるが、Base64化による文字列長・負荷があるため小さなプレビュー向きtoBlob():バイナリデータとして扱えるため、ファイルの保存・ダウンロードや大きな画像の処理に向いている- ダウンロードの実装:
toBlob()+URL.createObjectURL()+a[download]の組み合わせが定石 - 画像形式の注意:
qualityによる容量調整はJPEG/WebPで有効。PNGでは画質指定による圧縮は効かない - セキュリティ仕様:別オリジンの画像を描画したCanvasではCORS起因の
SecurityErrorに注意する(crossOriginはsrc設定前に指定)
Canvas操作ツールや画像ジェネレーターで画像の保存・ダウンロードを実装する場合は、まず toBlob() を候補にすると扱いやすいでしょう。