JavaScript/Blob

JavaScriptにおけるBlob

1. Blobとは

JavaScriptにおけるBlobは「Binary Large Object(バイナリラージオブジェクト)」の略称で、イミュータブル(不変)なバイナリデータを表現するためのオブジェクトです。画像、音声、動画などのマルチメディアファイル、PDFなどのドキュメント、あるいは任意のバイナリデータを扱う際に利用されます。

Blobは単なるバイナリデータの集合体ですが、MIMEタイプも持っているため、ブラウザはそのデータが何を表しているのかを解釈できます。このMIMEタイプによって、ブラウザはデータをテキスト、画像、音声などとして適切に処理できるのです。

Blobオブジェクトは、FileオブジェクトとFileReader APIとの連携によく用いられます。FileオブジェクトはBlobの特殊な形式で、ファイル名やファイルの最終更新日時などの追加的なメタデータを持っています。ユーザーがファイルをアップロードするHTML入力要素からFileオブジェクトにアクセスできます。

2. Blobの作成方法

コンストラクタを使った基本的な作成

Blobオブジェクトは、Blob() コンストラクタを使って作成できます。コンストラクタは2つの引数を取ります。1つ目は配列形式のデータパーツ、2つ目はオプションのオブジェクトです。

// 単純な文字列からBlobを作成
const myBlob = new Blob(['Hello, world!'], { type: 'text/plain' });

console.log(myBlob.size); // 13 (バイト数)
console.log(myBlob.type); // "text/plain"

複数のデータ片からひとつのBlobを作成することもできます。

// 複数のデータ片からBlobを作成
const part1 = 'こんにちは、';
const part2 = '世界!';
const myBlob = new Blob([part1, part2], { type: 'text/plain;charset=UTF-8' });

様々なデータ型からBlobを作成する

Blobコンストラクタには、文字列だけでなく、ArrayBufferTypedArrayDataView、別のBlob、あるいはこれらの組み合わせを渡すことができます。

// ArrayBufferからBlobを作成
const buffer = new ArrayBuffer(8);
const bufferBlob = new Blob([buffer], { type: 'application/octet-stream' });

// Uint8ArrayからBlobを作成
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in ASCII
const arrayBlob = new Blob([uint8Array], { type: 'application/octet-stream' });

// 既存のBlobの一部を新しいBlobとして作成
const originalBlob = new Blob(['大きなテキストデータ'], { type: 'text/plain' });
const partialBlob = originalBlob.slice(0, 5); // 最初の5バイトだけを取得

3. Blobのプロパティとメソッド

Blobオブジェクトは限定的なプロパティとメソッドを持っています。基本的なプロパティとしては、sizetypeがあります。

プロパティ 説明
size Blobのサイズをバイト単位で表します
type BlobのMIMEタイプを表します(例: "text/plain", "image/jpeg")

Blobの主要なメソッドには以下のものがあります。

メソッド 説明
slice() Blobの一部を新しいBlobオブジェクトとして取得します
text() Blobの内容をテキストとして読み込むPromiseを返します
arrayBuffer() BlobをArrayBufferとして読み込むPromiseを返します
stream() BlobをReadableStreamとして取得します

これらのメソッドの使用例を見てみましょう。

// Blobを作成
const myBlob = new Blob(['JavaScript Blobの例です'], { type: 'text/plain' });

// slice()メソッドを使用して一部を取得
const partialBlob = myBlob.slice(0, 10, 'text/plain');
console.log(partialBlob.size); // 10

// text()メソッドを使用してテキストを取得
myBlob.text().then(text => {
  console.log('Blobのテキスト内容:', text);
});

// arrayBuffer()メソッドを使用
myBlob.arrayBuffer().then(buffer => {
  console.log('ArrayBufferの長さ:', buffer.byteLength);
  // バッファを処理...
});

// stream()メソッドを使用
const stream = myBlob.stream();
// ストリームを処理...

4. BlobとURLの連携

Blobの強力な機能の一つは、それをURLに変換できることです。URL.createObjectURL()メソッドを使って、BlobからURLを生成できます。このURLは、イメージの表示、ファイルのダウンロードやプレビューなど、様々な目的で使用できます。

// テキストBlobを作成
const textBlob = new Blob(['ファイルの内容をここに書きます'], { type: 'text/plain' });

// BlobからURLを作成
const blobUrl = URL.createObjectURL(textBlob);

// URLをリンク要素に設定
const link = document.createElement('a');
link.href = blobUrl;
link.download = 'sample.txt';
link.textContent = 'テキストファイルをダウンロード';
document.body.appendChild(link);

// メモリリークを防ぐため、使用後はURLを解放することが重要
link.onclick = () => {
  // クリック後にURLを解放
  setTimeout(() => {
    URL.revokeObjectURL(blobUrl);
    console.log('Blob URLを解放しました');
  }, 100);
};

Blob URLは、ブラウザのメモリ内に保持されます。したがって、不要になったらURL.revokeObjectURL()メソッドを呼び出してメモリを解放することが重要です。

Blobをデータ URL(Base64エンコードされたURL)に変換する場合は、FileReaderオブジェクトを使用できます。

const imageBlob = new Blob([imageData], { type: 'image/png' });
const reader = new FileReader();

reader.onload = function() {
  const dataUrl = reader.result;
  // データURLを使用(例: img要素のsrc属性に設定)
  const img = document.createElement('img');
  img.src = dataUrl;
  document.body.appendChild(img);
};

reader.readAsDataURL(imageBlob);

5. Blobの実践的な利用例

ファイルダウンロードの実装

BlobとURL.createObjectURLを組み合わせることで、クライアントサイドでファイルを生成し、ユーザーにダウンロードさせることができます。

function generateAndDownloadFile() {
  // CSVデータを生成
  const csvContent = "名前,年齢,職業\n山田太郎,30,エンジニア\n佐藤花子,25,デザイナー";
  
  // CSVデータからBlobを作成
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  
  // BlobからURLを作成
  const url = URL.createObjectURL(blob);
  
  // リンク要素を作成
  const link = document.createElement('a');
  link.href = url;
  link.download = 'ユーザーデータ.csv';
  
  // リンクを非表示で追加しクリック
  document.body.appendChild(link);
  link.click();
  
  // クリーンアップ
  document.body.removeChild(link);
  setTimeout(() => URL.revokeObjectURL(url), 100);
}

// ボタンにイベントリスナーを追加
document.getElementById('downloadButton').addEventListener('click', generateAndDownloadFile);

キャンバス要素との連携

HTMLのCanvasからBlobを作成して画像として保存することができます。

// キャンバス要素を取得
const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');

// キャンバスに何か描画する
context.fillStyle = 'blue';
context.fillRect(10, 10, 150, 150);
context.fillStyle = 'red';
context.font = '24px Arial';
context.fillText('Canvasからの画像', 15, 50);

// キャンバスの内容をBlobとして取得
canvas.toBlob(function(blob) {
  // Blobを使って何かする(例: ダウンロード)
  const url = URL.createObjectURL(blob);
  
  const link = document.createElement('a');
  link.href = url;
  link.download = 'canvas-image.png';
  link.click();
  
  URL.revokeObjectURL(url);
}, 'image/png');

6. FileReaderとBlobの連携

FileReaderは、Blobの内容を様々な形式で読み込むためのAPIを提供します。FileReaderはBlobを同期的に読み込むのではなく、非同期的に読み込みます。

const blob = new Blob(['こんにちは世界'], { type: 'text/plain' });
const reader = new FileReader();

// 読み込み完了時のイベントハンドラを設定
reader.onload = function(event) {
  console.log('読み込み結果:', event.target.result);
};

// エラー発生時のハンドラ
reader.onerror = function() {
  console.error('ファイル読み込みエラー:', reader.error);
};

// 読み込み開始時のハンドラ
reader.onloadstart = function() {
  console.log('読み込み開始');
};

// 読み込み終了時のハンドラ(成功・失敗どちらの場合も発生)
reader.onloadend = function() {
  console.log('読み込み終了(結果に関わらず)');
};

// テキストとして読み込み開始
reader.readAsText(blob);

FileReaderには、Blobを読み込むための4つの主要なメソッドがあります。

メソッド 説明
readAsText() Blobをテキストとして読み込みます
readAsArrayBuffer() BlobをArrayBufferとして読み込みます
readAsDataURL() BlobをデータURL(Base64エンコード)として読み込みます
readAsBinaryString() Blobをバイナリ文字列として読み込みます

FileReaderの代わりに、近年ではBlob.text()Blob.arrayBuffer()などのメソッドを使用することも増えています。これらはPromiseベースで扱いやすいという利点があります。

7. Fetch APIとBlobの活用

Fetch APIを使用して、サーバーからデータを取得し、それをBlobとして処理することができます。また、Blobをサーバーに送信することも可能です。

サーバーからBlobとしてデータを取得

// 画像をBlobとして取得
fetch('https://example.com/image.jpg')
  .then(response => {
    if (!response.ok) {
      throw new Error('ネットワークレスポンスが正常ではありません');
    }
    return response.blob(); // レスポンスをBlobとして取得
  })
  .then(imageBlob => {
    // Blobを使用(例: 画像として表示)
    const imageUrl = URL.createObjectURL(imageBlob);
    const imgElement = document.createElement('img');
    imgElement.src = imageUrl;
    document.body.appendChild(imgElement);
    
    // 使用後にURLを解放
    imgElement.onload = () => {
      URL.revokeObjectURL(imageUrl);
    };
  })
  .catch(error => {
    console.error('Fetch操作に問題が発生しました:', error);
  });

Blobをサーバーに送信

// フォームデータからBlobを作成
const formData = new FormData(document.getElementById('uploadForm'));
const fileField = document.querySelector('input[type="file"]');
const file = fileField.files[0]; // これはBlobの特殊なタイプです

// Blobをサーバーに送信
fetch('https://example.com/upload', {
  method: 'POST',
  body: file, // BlobまたはFileオブジェクトを直接送信
  headers: {
    'Content-Type': file.type
  }
})
.then(response => response.json())
.then(data => {
  console.log('アップロード成功:', data);
})
.catch(error => {
  console.error('アップロードエラー:', error);
});

8. WebワーカーとBlobの連携

WebワーカーはJavaScriptをバックグラウンドスレッドで実行するための仕組みです。Blobを使用して、文字列からWebワーカーを作成できます。

// ワーカースクリプトの内容を文字列として作成
const workerScript = `
  self.onmessage = function(e) {
    console.log('ワーカーがメッセージを受け取りました:', e.data);
    
    // 何か重い処理を実行
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += i;
    }
    
    // 結果を送り返す
    self.postMessage({
      result: result,
      originalData: e.data
    });
  };
`;

// スクリプト文字列からBlobを作成
const blob = new Blob([workerScript], { type: 'application/javascript' });

// BlobからURLを作成
const blobUrl = URL.createObjectURL(blob);

// URLを使ってワーカーを作成
const worker = new Worker(blobUrl);

// ワーカーとメッセージを交換
worker.onmessage = function(e) {
  console.log('メインスレッドがワーカーからメッセージを受け取りました:', e.data);
  
  // 使用後にURLを解放
  URL.revokeObjectURL(blobUrl);
  worker.terminate();
};

// ワーカーにメッセージを送信
worker.postMessage({ command: 'start', value: 42 });

このアプローチの利点は、Webワーカーのコードを別ファイルとして保存する必要がなく、JavaScriptコード内に直接埋め込めることです。

9. IndexedDBとBlobの保存

IndexedDBは、ブラウザ内にデータを永続的に保存するためのAPIです。Blobオブジェクトを含む大量のデータを保存することができます。

// データベースを開く
const request = indexedDB.open('myDatabase', 1);

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  // Blobを保存するオブジェクトストアを作成
  const objectStore = db.createObjectStore('blobStore', { keyPath: 'id' });
};

request.onsuccess = function(event) {
  const db = event.target.result;
  
  // 保存するBlobを作成
  const imageBlob = new Blob(['イメージデータ'], { type: 'image/jpeg' });
  
  // トランザクションを開始
  const transaction = db.transaction(['blobStore'], 'readwrite');
  const objectStore = transaction.objectStore('blobStore');
  
  // Blobをデータベースに追加
  const addRequest = objectStore.add({
    id: 'image1',
    name: 'サンプル画像',
    blob: imageBlob,
    date: new Date()
  });
  
  addRequest.onsuccess = function() {
    console.log('Blobが正常に保存されました');
  };
  
  // Blobを取得
  const getRequest = objectStore.get('image1');
  getRequest.onsuccess = function() {
    if (getRequest.result) {
      const savedBlob = getRequest.result.blob;
      console.log('保存されたBlobのサイズ:', savedBlob.size);
      console.log('保存されたBlobのタイプ:', savedBlob.type);
      
      // Blobを使用(例: URLを作成して表示)
      const url = URL.createObjectURL(savedBlob);
      // URLを使用...
      URL.revokeObjectURL(url);
    }
  };
  
  transaction.oncomplete = function() {
    console.log('トランザクション完了');
    db.close();
  };
};

request.onerror = function(event) {
  console.error('データベースエラー:', event.target.error);
};

大きなBlobを扱う場合は、IndexedDBのストレージ制限に注意する必要があります。ブラウザごとに異なる制限があり、ユーザーの許可なしに大量のデータを保存することはできません。

10. セキュリティの考慮事項

Blobを扱う際には、いくつかのセキュリティ上の考慮事項があります。

クロスオリジンの制約

ブラウザのセキュリティポリシーにより、異なるオリジンからのリソースを読み取る際には制限があります。別ドメインからの画像をCanvasに描画し、そのデータをBlobとして取得しようとする場合、「汚染された(tainted)」キャンバスになり、.toBlob()メソッドが失敗することがあります。

const img = new Image();
img.crossOrigin = 'anonymous'; // 重要: クロスオリジンリクエストを許可
img.onload = function() {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  
  // crossOrigin設定がなければ、ここでセキュリティエラーが発生する可能性があります
  canvas.toBlob(function(blob) {
    console.log('画像をBlobとして取得:', blob);
  });
};

img.src = 'https://example.com/image.jpg'; // 別オリジンの画像

ユーザーデータの安全な取り扱い

ユーザーからのファイルアップロードを処理する際は、潜在的な悪意のあるコンテンツに注意する必要があります。

// ファイル入力からファイルを取得
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function(e) {
  const file = e.target.files[0];
  
  // ファイルタイプを検証
  if (!file.type.match('image.*')) {
    alert('画像ファイルを選択してください');
    return;
  }
  
  // ファイルサイズを制限
  const maxSize = 5 * 1024 * 1024; // 5MB
  if (file.size > maxSize) {
    alert('ファイルサイズは5MB以下にしてください');
    return;
  }
  
  // 安全なファイルのみ処理を続行
  const reader = new FileReader();
  reader.onload = function(event) {
    // ファイルの内容を処理...
  };
  reader.readAsDataURL(file);
});

11. パフォーマンスの最適化

Blobを扱う際には、特に大きなファイルを扱う場合、パフォーマンスに注意する必要があります。

メモリ使用量の管理

// メモリ使用量を管理する例
function processLargeFile(file) {
  // ファイルを小さな部分に分割して処理
  const chunkSize = 2 * 1024 * 1024; // 2MB
  let offset = 0;
  
  function readNextChunk() {
    if (offset >= file.size) {
      console.log('ファイル処理完了');
      return;
    }
    
    const chunk = file.slice(offset, offset + chunkSize);
    const reader = new FileReader();
    
    reader.onload = function(e) {
      // チャンクを処理...
      console.log(`${offset}-${offset + chunk.size}バイトを処理`);
      
      // 次のチャンクに移動
      offset += chunkSize;
      setTimeout(readNextChunk, 0); // UIブロックを防ぐ
    };
    
    reader.readAsArrayBuffer(chunk);
  }
  
  readNextChunk();
}

// 使用例
const fileInput = document.getElementById('largeFileInput');
fileInput.addEventListener('change', function(e) {
  const file = e.target.files[0];
  processLargeFile(file);
});

Blob URLの適切な管理

Blob URLを作成したら、不要になった時点で解放することが重要です。解放されないURLはメモリリークの原因になります。

// Blob URLを管理するヘルパー関数
const blobUrlManager = {
  urls: new Set(),
  
  create(blob) {
    const url = URL.createObjectURL(blob);
    this.urls.add(url);
    return url;
  },
  
  revoke(url) {
    URL.revokeObjectURL(url);
    this.urls.delete(url);
  },
  
  revokeAll() {
    this.urls.forEach(url => URL.revokeObjectURL(url));
    this.urls.clear();
  }
};

// 使用例
const blob = new Blob(['テストデータ'], { type: 'text/plain' });
const url = blobUrlManager.create(blob);

// 使用後に解放
blobUrlManager.revoke(url);

// ページ離脱時に全て解放
window.addEventListener('beforeunload', () => blobUrlManager.revokeAll());

12. まとめと発展的な話題

まとめ

Blobは、JavaScriptでバイナリデータを扱うための強力な機能を提供します。本章では、以下の内容を学びました。

  • Blobの基本概念と作成方法
  • Blobのプロパティとメソッド
  • BlobとURLの連携
  • 実践的な利用例
  • FileReader、Fetch API、Webワーカー、IndexedDBとの連携
  • セキュリティとパフォーマンスの考慮事項

Stream APIとの関係

Webプラットフォームは進化を続けており、Stream APIはBlobの扱い方に革命をもたらしています。Stream APIを使用すると、大きなデータを小さな塊で処理でき、メモリ効率が向上します。

// Blobからストリームを取得
const blob = new Blob(['大量のテキストデータ...'], { type: 'text/plain' });
const stream = blob.stream();

// ReadableStreamを使った処理
const reader = stream.getReader();

async function processStream() {
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        console.log('ストリーム読み込み完了');
        break;
      }
      console.log('チャンク読み込み:', value);
      // チャンクを処理...
    }
  } catch (error) {
    console.error('ストリーム処理エラー:', error);
  }
}

processStream();

将来的な発展の方向性

WebプラットフォームとJavaScriptエコシステムの進化に伴い、Blobの扱い方も変わっています。

  • File System Access API: より直接的なファイルシステムアクセスが可能になります。
  • WebCodecs API: 動画や音声のエンコード・デコードをより効率的に行えます。
  • WebGPU: グラフィックス処理のためのBlobの使用が進化する可能性があります。

最新のWeb標準とAPIを把握し、適切なところでBlobを活用することで、より効率的で堅牢なWebアプリケーションを構築できるでしょう。

附録

静的プロパティ

Blob.arguments
Blob.caller
Blob.length
Blob.name
Blob.prototype

静的アクセサ

静的メソッド

Blobのインスタンスプロパティ

Blob.prototype [ Symbol.toStringTag ]

Blobのインスタンスアクセサ

get Blob.prototype.size
get Blob.prototype.type

Blobのインスタンスメソッド

Blob.prototype.arrayBuffer()
Blob.prototype.constructor()
Blob.prototype.slice()
Blob.prototype.stream()
Blob.prototype.text()
カテゴリ:JavaScript
カテゴリ:JavaScript