タイトル: File API
SEOタイトル: Web File API 完全ガイド|File/Blob/FileReader/Drag&Drop/File System Access
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>File API</strong> はブラウザ上でローカルファイルを安全に扱う Web 標準</li><li>主要オブジェクト: <code>File</code> / <code>FileList</code> / <code>Blob</code> / <code>FileReader</code> / <code>URL.createObjectURL</code></li><li>読み取り: <code>readAsText</code> / <code>readAsDataURL</code> / <code>readAsArrayBuffer</code></li><li>入力: <code><input type="file"></code> / <strong>Drag & Drop</strong> / <strong>Clipboard</strong></li><li>モダン: <strong>File System Access API</strong> (Chrome) で読み書き可能、<strong>IndexedDB</strong> と連携</li></ul></td></tr></table>
<h2>File API とは</h2>
<p><strong>File API</strong> は、ブラウザ JavaScript からローカルファイルを<strong>安全に読み書き</strong>するための W3C / WHATWG 標準 API 群です。同一オリジンポリシーやユーザー操作(クリック・ドロップ)を起点とする原則により、ユーザーの明示的な許可なしに任意ファイルにアクセスできない設計です。</p>
<p>関連仕様: <strong>File API</strong> / <strong>FileReader API</strong> / <strong>HTML5 Drag and Drop API</strong> / <strong>File System Access API</strong> / <strong>Streams API</strong>。</p>
<h2>基本オブジェクト</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>オブジェクト</th><th>説明</th></tr></thead>
<tbody>
<tr><td><code>Blob</code></td><td>任意バイナリデータ。サイズ・MIME を持つ</td></tr>
<tr><td><code>File</code></td><td>Blob の拡張、ファイル名・最終更新日を追加</td></tr>
<tr><td><code>FileList</code></td><td>File の配列 (input.files / DataTransfer.files)</td></tr>
<tr><td><code>FileReader</code></td><td>ファイルを非同期で読み取るリーダ</td></tr>
<tr><td><code>URL.createObjectURL</code></td><td>Blob/File を一時 URL に変換 (img/video 等で使用)</td></tr>
<tr><td><code>FormData</code></td><td>multipart/form-data として fetch でアップロード</td></tr>
</tbody>
</table>
<h2>ファイル選択 (input type=file)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><input type="file" id="picker" accept="image/*" multiple>
<script>
const picker = document.getElementById('picker');
picker.addEventListener('change', (e) => {
const files = e.target.files; // FileList
for (const file of files) {
console.log(file.name, file.size, file.type, file.lastModified);
}
});
</script></code></pre>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>属性</th><th>説明</th></tr></thead>
<tbody>
<tr><td><code>accept</code></td><td>MIME や拡張子で制限 (例: <code>image/*,.pdf</code>)</td></tr>
<tr><td><code>multiple</code></td><td>複数選択許可</td></tr>
<tr><td><code>capture</code></td><td>モバイルでカメラ起動 (<code>user</code> / <code>environment</code>)</td></tr>
<tr><td><code>webkitdirectory</code></td><td>ディレクトリ単位選択 (Chromium/Firefox)</td></tr>
</tbody>
</table>
<h2>FileReader で内容を読む</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const reader = new FileReader();
// テキストとして読む
reader.onload = () => console.log(reader.result);
reader.readAsText(file, 'UTF-8');
// データ URL (Base64) として読む → img.src へ
reader.onload = () => document.getElementById('img').src = reader.result;
reader.readAsDataURL(file);
// ArrayBuffer として読む (バイナリ操作)
reader.onload = () => {
const buf = reader.result;
const view = new Uint8Array(buf);
console.log('first 4 bytes:', view.slice(0, 4));
};
reader.readAsArrayBuffer(file);
// 進捗・エラー
reader.onprogress = (e) => {
if (e.lengthComputable) console.log((e.loaded / e.total * 100).toFixed(1) + '%');
};
reader.onerror = () => console.error(reader.error);
// 中断
reader.abort();</code></pre>
<h3>モダンな書き方 (await + File メソッド)</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// File は自身にメソッドを持つ (modern browsers)
const text = await file.text();
const buffer = await file.arrayBuffer();
const stream = file.stream(); // ReadableStream
// 巨大ファイルをチャンク処理
const reader = file.stream().getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('chunk', value.byteLength);
}</code></pre>
<h2>URL.createObjectURL で表示</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const url = URL.createObjectURL(file);
document.getElementById('preview').src = url;
// 不要になったら解放 (メモリリーク防止)
img.onload = () => URL.revokeObjectURL(url);</code></pre>
<h2>Drag & Drop</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><div id="drop" style="border:2px dashed #888;padding:40px;text-align:center">
ここにファイルをドロップ
</div>
<script>
const drop = document.getElementById('drop');
drop.addEventListener('dragover', (e) => {
e.preventDefault(); // 必須: drop を有効化
drop.style.background = '#eef';
});
drop.addEventListener('dragleave', () => drop.style.background = '');
drop.addEventListener('drop', (e) => {
e.preventDefault();
drop.style.background = '';
const files = e.dataTransfer.files;
for (const f of files) console.log(f.name);
});
</script></code></pre>
<h2>クリップボード経由</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// 貼り付けイベントから画像取得
document.addEventListener('paste', async (e) => {
for (const item of e.clipboardData.items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
console.log('pasted image:', file.name, file.size);
}
}
});
// クリップボードに書き込み (要 user gesture)
const blob = new Blob(['hello'], { type: 'text/plain' });
await navigator.clipboard.write([new ClipboardItem({ 'text/plain': blob })]);</code></pre>
<h2>fetch でアップロード</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// FormData (multipart)
const fd = new FormData();
fd.append('upload', file, file.name);
fd.append('title', 'sample');
const res = await fetch('/api/upload', { method: 'POST', body: fd });
// Blob を直接 PUT
await fetch('/api/upload', {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
// チャンク分割アップロード
const CHUNK = 5 * 1024 * 1024; // 5MB
for (let i = 0; i < file.size; i += CHUNK) {
const chunk = file.slice(i, i + CHUNK);
await fetch('/api/upload-chunk?offset=' + i, { method: 'POST', body: chunk });
}</code></pre>
<h2>ダウンロード (Blob 経由)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">function downloadBlob(blob, filename) {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
}
// CSV をその場で生成してダウンロード
const csv = 'name,score\nAlice,80\nBob,75';
downloadBlob(new Blob([csv], { type: 'text/csv' }), 'scores.csv');</code></pre>
<h2>File System Access API (Chrome 系)</h2>
<p>従来の File API が読み取り中心なのに対し、<strong>File System Access API</strong> はユーザーの許可下でローカルファイル/ディレクトリへの<strong>書き込み</strong>も可能にします (Chromium 系のみ)。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// ファイルを開いて読み書き
const [handle] = await window.showOpenFilePicker({
types: [{ description: 'Text', accept: { 'text/plain': ['.txt'] } }],
});
const file = await handle.getFile();
console.log(await file.text());
// 同じ場所に書き戻し
const w = await handle.createWritable();
await w.write('updated content');
await w.close();
// ディレクトリ選択
const dir = await window.showDirectoryPicker();
for await (const [name, h] of dir.entries()) {
console.log(name, h.kind); // 'file' or 'directory'
}
// 新規ファイル保存
const saveHandle = await window.showSaveFilePicker({
suggestedName: 'note.txt',
types: [{ accept: { 'text/plain': ['.txt'] } }],
});
const ws = await saveHandle.createWritable();
await ws.write('hello');
await ws.close();</code></pre>
<h2>IndexedDB との連携</h2>
<p>Blob は IndexedDB に直接格納できるため、オフラインキャッシュやローカル DB と組み合わせやすい:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const db = await openDB('files', 1, {
upgrade(db) { db.createObjectStore('store', { keyPath: 'name' }); },
});
await db.put('store', { name: file.name, blob: file });
const saved = await db.get('store', 'photo.jpg');
const url = URL.createObjectURL(saved.blob);</code></pre>
<h2>セキュリティ・制限事項</h2>
<ul>
<li><strong>同一オリジンポリシー</strong>: ドラッグ元クロスオリジンの blob URL は使えないことがある</li>
<li><strong>ユーザー操作起点</strong>: showOpenFilePicker / showSaveFilePicker はクリックなど gesture 内でのみ呼び出し可能</li>
<li><strong>サイズ制限</strong>: ブラウザの IndexedDB / メモリ制限。大容量は Streams API でチャンク処理</li>
<li><strong>ファイル名は安全に</strong>: アップロード後はサーバ側で正規化 (拡張子強制、ハッシュ名)</li>
</ul>
<h2>ブラウザ対応</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>API</th><th>Chrome</th><th>Firefox</th><th>Safari</th></tr></thead>
<tbody>
<tr><td>FileReader / Blob / File</td><td>○</td><td>○</td><td>○</td></tr>
<tr><td>file.text() / arrayBuffer()</td><td>○</td><td>○</td><td>○</td></tr>
<tr><td>Drag & Drop</td><td>○</td><td>○</td><td>○</td></tr>
<tr><td>Clipboard image paste</td><td>○</td><td>○</td><td>○</td></tr>
<tr><td>File System Access</td><td>○</td><td>×</td><td>×</td></tr>
<tr><td>webkitdirectory</td><td>○</td><td>○</td><td>△</td></tr>
</tbody>
</table>
<h2>FAQ</h2>
<p><strong>Q: Base64 (DataURL) と Blob URL、どちらを使うべき?</strong><br />
A: <strong>表示用には Blob URL</strong>。Base64 はメモリ・転送効率が悪い (約 33% 増)。サーバへ JSON 形式で送る必要があるときだけ Base64。</p>
<p><strong>Q: ローカルに自由に書き込めないの?</strong><br />
A: 任意の場所は不可。File System Access API でユーザー許可を得た範囲のみ可能。それ以外は IndexedDB / OPFS (Origin Private File System) を使う。</p>
<p><strong>Q: 巨大ファイル (1GB 超) をアップロードしたい</strong><br />
A: <code>file.slice()</code> でチャンク化し、サーバ側で結合。S3 マルチパートアップロードや tus プロトコルを使うと冪等で堅牢。</p>