2022年5月16日

Blob

ArrayBufferとビューはECMA標準、JavaScriptの一部です。

ブラウザには、File APIで説明されている、特にBlobなど、追加のより高レベルのオブジェクトがあります。

Blobは、オプションの文字列type(通常はMIMEタイプ)、およびblobParts(他のBlobオブジェクト、文字列、BufferSourceのシーケンス)で構成されます。

コンストラクタの構文は次のとおりです。

new Blob(blobParts, options);
  • blobParts は、Blob/BufferSource/String値の配列です。
  • options オプションオブジェクト
    • typeBlobの型、通常はMIMEタイプ(例:image/png)、
    • endingsBlobを現在のOSの改行コード(\r\nまたは\n)に合わせるために、改行コードを変換するかどうか。デフォルトは"transparent"(何もせず)、"native"(変換する)も可能です。

例として

// create Blob from a string
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in binary form

let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});

Blobのスライスは、次のように抽出できます。

blob.slice([byteStart], [byteEnd], [contentType]);
  • byteStart – 開始バイト、デフォルトは0。
  • byteEnd – 最後のバイト(排他的、デフォルトは最後まで)。
  • contentType – 新しいblobのtype、デフォルトはソースと同じ。

引数はarray.sliceに似ており、負の数も許容されます。

Blobオブジェクトは不変です。

Blob内のデータを直接変更することはできませんが、Blobの一部をスライスしたり、それらから新しいBlobオブジェクトを作成したり、それらを新しいBlobに混ぜ合わせたりすることができます。

この動作はJavaScriptの文字列と似ています。文字列内の文字を変更することはできませんが、新しい修正された文字列を作成することはできます。

BlobをURLとして

Blobは、その内容を表示するために、<a><img>、その他のタグのURLとして簡単に使用できます。

typeのおかげで、Blobオブジェクトのダウンロード/アップロードも可能になり、typeはネットワークリクエストで自然にContent-Typeになります。

簡単な例から始めましょう。リンクをクリックすると、「hello world」の内容を持つ動的に生成されたBlobをファイルとしてダウンロードします。

<!-- download attribute forces the browser to download instead of navigating -->
<a download="hello.txt" href='#' id="link">Download</a>

<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);
</script>

JavaScriptで動的にリンクを作成し、link.click()でクリックをシミュレートすることもでき、ダウンロードが自動的に開始されます。

HTMLを使用せずに、動的に作成されたBlobをユーザーにダウンロードさせる同様のコードを次に示します。

let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Hello, world!'], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);

link.click();

URL.revokeObjectURL(link.href);

URL.createObjectURLBlobを受け取り、blob:<origin>/<uuid>という形式の一意のURLを作成します。

link.hrefの値は次のようになります。

blob:https://javascriptinfo.dokyumento.jp/1e67e00e-860d-40a5-89ae-6ab0cbee6273

URL.createObjectURLによって生成された各URLについて、ブラウザはURL→Blobのマッピングを内部的に保存します。そのため、このようなURLは短くなりますが、Blobにアクセスできます。

生成されたURL(およびそれに対応するリンク)は、開いている現在のドキュメント内でのみ有効です。そして、それは<img><a>、基本的にURLを期待する他のオブジェクトでBlobを参照することを可能にします。

ただし、副作用があります。Blobのマッピングがある間、Blob自体はメモリ内に存在します。ブラウザはそれを解放できません。

ドキュメントのアンロード時にマッピングは自動的にクリアされるため、Blobオブジェクトはその時点で解放されます。しかし、アプリケーションが長寿命である場合、それはすぐに起こりません。

したがって、URLを作成すると、Blobは、もはや必要でなくても、メモリ内に残ります。

URL.revokeObjectURL(url)は、内部マッピングからの参照を削除するため、Blobを削除し(他の参照がない場合)、メモリを解放できます。

最後の例では、Blobを一度だけ、即時ダウンロードのために使用することを意図しているため、URL.revokeObjectURL(link.href)をすぐに呼び出します。

クリック可能なHTMLリンクを含む前の例では、URL.revokeObjectURL(link.href)を呼び出しません。これは、BlobのURLが無効になるためです。取り消し後、マッピングが削除されると、URLは機能しなくなります。

Blobをbase64に変換

URL.createObjectURLの代替手段として、Blobをbase64エンコードされた文字列に変換する方法があります。

このエンコーディングは、バイナリデータを、0〜64のASCIIコードを持つ、非常に安全な「読み取り可能な」文字の文字列として表します。そして、さらに重要なのは、このエンコーディングを「data-url」で使用できることです。

data urldata:[<mediatype>][;base64],<data>という形式です。「通常の」URLと同様に、どこでも使用できます。

例えば、スマイリーフェイスを次に示します。

<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">

ブラウザは文字列をデコードして画像を表示します:

Blobをbase64に変換するには、組み込みのFileReaderオブジェクトを使用します。これは、複数のフォーマットでBlobからデータを読み取ることができます。次の章では、より詳細に説明します。

base-64経由でのblobのダウンロードデモを次に示します。

let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Hello, world!'], {type: 'text/plain'});

let reader = new FileReader();
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload

reader.onload = function() {
  link.href = reader.result; // data url
  link.click();
};

BlobのURLを作成する両方の方法は使用できますが、通常はURL.createObjectURL(blob)の方がシンプルで高速です。

URL.createObjectURL(blob)
  • メモリを気にしている場合は、それらを呼び消す必要があります。
  • Blobへの直接アクセス、「エンコード/デコード」なし
Blobをdata urlに変換
  • 何も呼び消す必要はありません。
  • 大きなBlobオブジェクトのエンコーディングにおけるパフォーマンスとメモリ損失。

画像をBlobに変換

画像、画像の一部、またはページのスクリーンショットのBlobを作成できます。これは、どこかにアップロードする場合に便利です。

画像操作は<canvas>要素を使用して行われます。

  1. canvas.drawImageを使用して、キャンバスに画像(またはその一部)を描画します。
  2. Blobを作成し、完了したらそれを使用してcallbackを実行するキャンバスメソッド.toBlob(callback, format, quality)を呼び出します。

以下の例では、画像は単にコピーされていますが、blobを作成する前に、キャンバス上で画像を切り取ったり変換したりすることもできます。

// take any image
let img = document.querySelector('img');

// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;

let context = canvas.getContext('2d');

// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas

// toBlob is async operation, callback is called when done
canvas.toBlob(function(blob) {
  // blob ready, download it
  let link = document.createElement('a');
  link.download = 'example.png';

  link.href = URL.createObjectURL(blob);
  link.click();

  // delete the internal blob reference, to let the browser clear memory from it
  URL.revokeObjectURL(link.href);
}, 'image/png');

コールバックの代わりにasync/awaitを使用する場合は

let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));

https://github.com/niklasvh/html2canvasなどのライブラリを使用すると、ページのスクリーンショットを撮ることができます。これは、ページを巡回し、<canvas>に描画するだけです。その後、上記と同じ方法でBlobを取得できます。

BlobからArrayBufferへ

Blobコンストラクタを使用すると、あらゆるBufferSourceを含む、ほぼあらゆるものからblobを作成できます。

しかし、低レベルの処理を行う必要がある場合は、blob.arrayBuffer()から最下位のArrayBufferを取得できます。

// get arrayBuffer from blob
const bufferPromise = await blob.arrayBuffer();

// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);

Blobからストリームへ

2GBを超えるblobの読み書きを行う場合、arrayBufferの使用はメモリ集約的になります。この時点で、blobをストリームに直接変換できます。

ストリームは、部分的に読み取り(または書き込み)できる特殊なオブジェクトです。ここでは範囲外ですが、例を以下に示し、https://developer.mozilla.org/en-US/docs/Web/API/Streams_APIで詳細を読むことができます。ストリームは、部分的に処理するのに適したデータに便利です。

Blobインターフェースのstream()メソッドは、読み取り時にBlobに含まれるデータを返すReadableStreamを返します。

その後、次のようにして読み取ることができます。

// get readableStream from blob
const readableStream = blob.stream();
const stream = readableStream.getReader();

while (true) {
  // for each iteration: value is the next blob fragment
  let { done, value } = await stream.read();
  if (done) {
    // no more data in the stream
    console.log('all blob processed.');
    break;
  }

   // do something with the data portion we've just read from the blob
  console.log(value);
}

まとめ

ArrayBufferUint8Array、その他のBufferSourceは「バイナリデータ」ですが、Blobは「型付きバイナリデータ」を表します。

これにより、ブラウザで非常に一般的なアップロード/ダウンロード操作にBlobが便利です。

XMLHttpRequestfetchなど、Webリクエストを実行するメソッドは、他のバイナリ型と同様に、Blobをネイティブに使用できます。

Blobと低レベルのバイナリデータ型の間を簡単に変換できます。

  • new Blob(...)コンストラクタを使用して、型付き配列からBlobを作成できます。
  • blob.arrayBuffer()を使用してBlobからArrayBufferを取得し、低レベルのバイナリ処理のためにその上にビューを作成できます。

変換ストリームは、大きなBlobを扱う必要がある場合に非常に便利です。BlobからReadableStreamを簡単に作成できます。Blobインターフェースのstream()メソッドはReadableStreamを返し、読み取り時にBlobに含まれるデータが返されます。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善点の提案がある場合は、コメントする代わりにGitHub issueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合は、サンドボックス(plnkrjsbincodepen…)を使用してください。