「ダウンロード処理」は“ブラウザの中のデータ”を“ユーザーのファイル”に変えること
JavaScript でいう「ダウンロード処理」は、
ブラウザの中にあるデータ(文字列・JSON・画像・Blob など)を、ユーザーにファイルとして保存させること です。
サーバーから来たデータをそのまま保存させることもあれば、
ブラウザ内で生成したテキストや画像、集計結果などを
「ユーザーの手元にファイルとして渡す」こともあります。
ここでよく使う道具は主に 3 つです。
Blob(データのかたまりを表す)URL.createObjectURL(Blob に一時的な URL を貼る)<a download>(リンクを「保存ダイアログ」に変える)
この 3 つを組み合わせると、
「ブラウザ内のデータ → ダウンロードファイル」という流れを自在に作れます。
一番基本のパターン:文字列 → Blob → createObjectURL → a.download
文字列をテキストファイルとしてダウンロードさせる
まずは、最もシンプルで強力なパターンから。
HTML:
<button id="download">テキストをダウンロード</button>
JavaScript:
const btn = document.querySelector("#download");
btn.addEventListener("click", () => {
const text = "こんにちは\nこれはダウンロード用に作ったテキストです。";
const blob = new Blob([text], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "sample.txt";
a.click();
URL.revokeObjectURL(url);
});
JavaScriptここで起きていることを順番に分解すると、こうなります。
ブラウザ内の文字列 text を Blob に変換する
Blob に対して URL.createObjectURL で一時 URL を発行する<a> 要素を作り、その href にその URL をセットするa.download = "sample.txt" で「保存時のファイル名」を指定するa.click() で擬似的にクリックして、ブラウザに「ダウンロード開始」をさせる
使い終わった URL を URL.revokeObjectURL(url) で解放する
ここでの超重要ポイントは、
「ブラウザ内のデータを“ファイルとして保存させる”には、
一度 Blob にして、URL を発行し、<a download> に渡す」
という流れを作る、ということです。
JSON や CSV など「アプリのデータ」をダウンロードする
JSON オブジェクトを data.json として保存させる
HTML:
<button id="download-json">JSON をダウンロード</button>
JavaScript:
const btn = document.querySelector("#download-json");
btn.addEventListener("click", () => {
const data = {
name: "太郎",
age: 25,
skills: ["JavaScript", "HTML", "CSS"]
};
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "data.json";
a.click();
URL.revokeObjectURL(url);
});
JavaScriptここでのポイントは、
アプリ内のオブジェクトを JSON 文字列に変換する
JSON 文字列から Blob を作る
あとはさっきと同じ「Blob → URL → a.download」の流れ
ということです。
「アプリの状態をエクスポートする」
「設定を JSON として保存させる」
といった機能は、このパターンで実現できます。
CSV も同じノリでいける
例えば、配列の配列を CSV にしてダウンロードする場合も同じです。
const rows = [
["name", "age"],
["太郎", 25],
["花子", 22]
];
const csv = rows.map(cols => cols.join(",")).join("\n");
const blob = new Blob([csv], { type: "text/csv" });
// あとは同じ:createObjectURL → a.download
JavaScript「どんな形式のデータでも、最終的には文字列にして Blob に入れればファイルにできる」
という感覚を持っておくと、応用が効きます。
画像やバイナリのダウンロード:Blob をそのまま使う
Canvas から画像ファイルとして保存させる
Canvas と組み合わせると、「描いたものを画像として保存」ができます。
HTML:
<canvas id="canvas" width="200" height="200"></canvas>
<button id="save">画像として保存</button>
JavaScript:
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const btn = document.querySelector("#save");
ctx.fillStyle = "skyblue";
ctx.fillRect(0, 0, 200, 200);
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
btn.addEventListener("click", () => {
canvas.toBlob((blob) => {
if (!blob) return;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "canvas.png";
a.click();
URL.revokeObjectURL(url);
}, "image/png");
});
JavaScriptここでは、
canvas.toBlob で Canvas の内容を画像 Blob に変換
その Blob を createObjectURL で URL にするa.download でファイル名を指定してダウンロード
という流れです。
「ブラウザで描いたものを、そのままユーザーのファイルにする」
というのは、ダウンロード処理のとても分かりやすい応用例です。
サーバーから fetch したバイナリをそのまま保存させる
サーバーからファイルを取得して、そのままユーザーに保存させることもできます。
async function downloadFromServer() {
const res = await fetch("/api/report");
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "report.pdf";
a.click();
URL.revokeObjectURL(url);
}
JavaScriptここでは、
fetch でレスポンスを受け取るres.blob() で中身を Blob として取得
あとは同じく「Blob → URL → a.download」
という流れです。
「サーバー側はファイルを返すだけ、
クライアント側はそれをダウンロードさせる UI を持つ」
という役割分担ができます。
重要ポイント:download 属性と「通常のリンク」との違い
通常のリンクは「開く」、download は「保存させる」
普通のリンクはこうです。
<a href="/files/manual.pdf">マニュアル</a>
クリックすると、
ブラウザはその URL を開こうとします(PDF ビューアなど)。
一方、download 属性を付けると、
「開く」ではなく「保存させる」方向に動きます。
<a href="/files/manual.pdf" download="manual.pdf">マニュアルをダウンロード</a>
これを JavaScript で動的に作っているのが、
さっきの a.download = "xxx" のコードです。
download 属性は、
「このリンクは“開く”というより“保存させたい”」
という意図をブラウザに伝えるスイッチだと捉えてください。
サーバー側の Content-Disposition との関係
実務では、サーバー側でContent-Disposition: attachment; filename="..."
というヘッダを付けて「ダウンロードさせる」ことも多いです。
クライアント側で download 属性を使うか、
サーバー側でヘッダを付けるか、
あるいは両方使うかは設計次第ですが、
「クライアント側だけでも、Blob と download 属性でかなりのことができる」
というのは覚えておいて損はありません。
重要ポイント:メモリと URL.revokeObjectURL の話
createObjectURL は「ブラウザに URL と Blob の対応を覚えさせる」
URL.createObjectURL(blob) を呼ぶと、
ブラウザは内部的に「この URL が来たらこの Blob を返す」という対応を保持します。
これ自体は便利ですが、
大量に作って放置するとメモリを食い続けます。
そこで、
「もうこの URL は使わない」タイミングで URL.revokeObjectURL(url) を呼ぶ
という習慣をつけておくと安心です。
いつ解放すればいいか
ダウンロード用の URL:
a.click() でダウンロードを開始した直後に revoke して OK
ユーザーのダウンロードは既に始まっているので問題ない
プレビュー用の URL:
その URL を参照している <img> や <a> を DOM から消したタイミングで revoke
プレビューを更新するときに、古い URL をまとめて revoke
「参照している要素がもうないなら、URL も要らない」
という感覚で考えると分かりやすいです。
初心者として「ダウンロード処理」で本当に掴んでほしいこと
ブラウザ内のデータをファイルとして保存させるには、
基本的にこの流れを踏みます。
データ(文字列・JSON・バイナリ)を用意する
それを Blob に包む(new Blob([...], { type }))URL.createObjectURL(blob) で一時 URL を作る<a> 要素の href にその URL を入れ、download でファイル名を指定するa.click() でダウンロードを開始し、使い終わった URL を revoke する
まずは次の 2 つを、自分の手で書いてみてください。
テキストを sample.txt としてダウンロードさせるボタン
JSON オブジェクトを data.json としてダウンロードさせるボタン
この 2 つがスムーズに書けるようになったとき、
「ダウンロード処理」は単なるおまけ機能ではなく、
「Web アプリの中のデータを、ユーザーの手元にちゃんと返すための出口」
として、かなり頼もしい存在に見えてくるはずです。
