5.

Web File APIの使い方|File・Blob・FileReader・ドラッグ&ドロップ

編集
この記事の要点
  • File API はブラウザ上でローカルファイルを安全に扱う Web 標準
  • 主要オブジェクト: File / FileList / Blob / FileReader / URL.createObjectURL
  • 読み取り: readAsText / readAsDataURL / readAsArrayBuffer
  • 入力: <input type="file"> / Drag & Drop / Clipboard
  • モダン: File System Access API (Chrome) で読み書き可能、IndexedDB と連携

File API とは

File API は、ブラウザ JavaScript からローカルファイルを安全に読み書きするための W3C / WHATWG 標準 API 群です。同一オリジンポリシーやユーザー操作(クリック・ドロップ)を起点とする原則により、ユーザーの明示的な許可なしに任意ファイルにアクセスできない設計です。

関連仕様: File API / FileReader API / HTML5 Drag and Drop API / File System Access API / Streams API

基本オブジェクト

オブジェクト説明
Blob任意バイナリデータ。サイズ・MIME を持つ
FileBlob の拡張、ファイル名・最終更新日を追加
FileListFile の配列 (input.files / DataTransfer.files)
FileReaderファイルを非同期で読み取るリーダ
URL.createObjectURLBlob/File を一時 URL に変換 (img/video 等で使用)
FormDatamultipart/form-data として fetch でアップロード

ファイル選択 (input type=file)

<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>
属性説明
acceptMIME や拡張子で制限 (例: image/*,.pdf)
multiple複数選択許可
captureモバイルでカメラ起動 (user / environment)
webkitdirectoryディレクトリ単位選択 (Chromium/Firefox)

FileReader で内容を読む

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();

モダンな書き方 (await + File メソッド)

// 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);
}

URL.createObjectURL で表示

const url = URL.createObjectURL(file);
document.getElementById('preview').src = url;

// 不要になったら解放 (メモリリーク防止)
img.onload = () => URL.revokeObjectURL(url);

Drag & Drop

<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>

クリップボード経由

// 貼り付けイベントから画像取得
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 })]);

fetch でアップロード

// 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 });
}

ダウンロード (Blob 経由)

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');

File System Access API (Chrome 系)

従来の File API が読み取り中心なのに対し、File System Access API はユーザーの許可下でローカルファイル/ディレクトリへの書き込みも可能にします (Chromium 系のみ)。

// ファイルを開いて読み書き
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();

IndexedDB との連携

Blob は IndexedDB に直接格納できるため、オフラインキャッシュやローカル DB と組み合わせやすい:

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);

セキュリティ・制限事項

  • 同一オリジンポリシー: ドラッグ元クロスオリジンの blob URL は使えないことがある
  • ユーザー操作起点: showOpenFilePicker / showSaveFilePicker はクリックなど gesture 内でのみ呼び出し可能
  • サイズ制限: ブラウザの IndexedDB / メモリ制限。大容量は Streams API でチャンク処理
  • ファイル名は安全に: アップロード後はサーバ側で正規化 (拡張子強制、ハッシュ名)

ブラウザ対応

APIChromeFirefoxSafari
FileReader / Blob / File
file.text() / arrayBuffer()
Drag & Drop
Clipboard image paste
File System Access××
webkitdirectory

FAQ

Q: Base64 (DataURL) と Blob URL、どちらを使うべき?
A: 表示用には Blob URL。Base64 はメモリ・転送効率が悪い (約 33% 増)。サーバへ JSON 形式で送る必要があるときだけ Base64。

Q: ローカルに自由に書き込めないの?
A: 任意の場所は不可。File System Access API でユーザー許可を得た範囲のみ可能。それ以外は IndexedDB / OPFS (Origin Private File System) を使う。

Q: 巨大ファイル (1GB 超) をアップロードしたい
A: file.slice() でチャンク化し、サーバ側で結合。S3 マルチパートアップロードや tus プロトコルを使うと冪等で堅牢。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Canvas API
  2. Drag and Drop API
  3. History API
  4. WebStorage API
  5. File API
  6. System Information API

最近更新/作成されたページ