<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() を候補にすると扱いやすいでしょう。