JavaScriptでBase64文字列からBLOBを作成する


447

文字列にBase64でエンコードされたバイナリデータがあります。

const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

blob:このデータを含むURLを作成してユーザーに表示したいと思います。

const blob = new Blob(????, {type: contentType});
const blobUrl = URL.createObjectURL(blob);

window.location = blobUrl;

BLOBの作成方法がわかりません。

場合によっては、data:代わりにURL を使用することでこれを回避できます。

const dataUrl = `data:${contentType};base64,${b64Data}`;

window.location = dataUrl;

ただし、ほとんどの場合、data:URLは非常に大きくなります。


JavaScriptでBase64文字列をBLOBオブジェクトにデコードするにはどうすればよいですか?

回答:


789

このatob関数は、Base64でエンコードされた文字列を、バイナリデータの各バイトの文字を含む新しい文字列にデコードします。

const byteCharacters = atob(b64Data);

各文字のコードポイント(charCode)は、バイトの値になります。.charCodeAt文字列の各文字にメソッドを使用してこれを適用することにより、バイト値の配列を作成できます。

const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
}

このバイト値の配列をUint8Arrayコンストラクターに渡すことにより、実際の型付きバイト配列に変換できます。

const byteArray = new Uint8Array(byteNumbers);

これは、配列にラップしてBlobコンストラクタに渡すことにより、BLOBに変換できます。

const blob = new Blob([byteArray], {type: contentType});

上記のコードは機能します。ただし、byteCharacters一度にすべてではなく、より小さなスライスでを処理することにより、パフォーマンスを少し向上させることができます。私の大まかなテストでは、512バイトが適切なスライスサイズのようです。これにより、次の機能が得られます。

const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
const blob = b64toBlob(b64Data, contentType);
const blobUrl = URL.createObjectURL(blob);

window.location = blobUrl;

完全な例:


6
こんにちはジェレミー。私たちのウェブアプリケーションにはこのコードがあり、ダウンロードするファイルのサイズが大きくなるまで問題は発生しませんでした。そのため、ユーザーがChromeまたはIEを使用して100 MBを超えるファイルをダウンロードすると、本番サーバーでハングとクラッシュが発生しました。IEの次の行で、メモリ例外「var byteNumbers = new Array(slice.length)」が発生していることがわかりました。ただし、クロムでは、同じ問題を引き起こすforループでした。この問題に対する適切な解決策が見つからなかったため、window.openを使用して直接ファイルをダウンロードするようにしました。ここでいくつかの助けを提供できますか?
Akshay Raut

反応ネイティブでビデオファイルをbase64に変換する方法はありますか?私は画像ファイルを使用してなんとかすることができましたが、ビデオのための同じための解決策を見つけられませんでした。リンクは役に立ちますか、解決策にもなります。
Diksha235

だからatob()によって返される文字列に0を格納する問題はありませんか?
wcochran

これは、ChromeとFirefoxの一部のblobでは機能しませんでしたが、エッジで機能しました:/
Gragas Incoming

私のために働きました ** JSON解析エラーがスローされます:認識されないtoke '<' **ブラウザーで画像を作成して、base64文字列をチェックしました。助けが要る。
アマンディープ

272

以下は、依存関係やライブラリのない、より最小限の方法です。
新しいフェッチAPIが必要です。(使用できますか?

var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(console.log)

このメソッドを使用すると、ReadableStream、ArrayBuffer、テキスト、JSONも簡単に取得できます。

関数として:

const b64toBlob = (base64, type = 'application/octet-stream') => 
  fetch(`data:${type};base64,${base64}`).then(res => res.blob())

JeremyのES6同期バージョンに対して簡単なパフォーマンステストを行いました。
同期バージョンはしばらくの間UIをブロックします。devtoolを開いたままにすると、フェッチのパフォーマンスが低下する可能性があります


1
base64でエンコードされた文字列のサイズが大きい場合(OperaのURIサイズの制限である665536文字を超える場合など)でも、これは機能しますか?
Daniel Kats

1
わからない、私はそれがアドレスバーの制限になる可能性があることを知っていますが、レンダリングする必要がないので、AJAXで物事を行うことは例外かもしれません。あなたはそれをテストする必要があります。もしそれが私にあったら、そもそもbase64文字列を取得することはなかっただろう。これを悪い習慣だと考えると、デコードとエンコードに多くのメモリと時間がかかります。createObjectURL代わりにreadAsDataURL、例えばより良いです。あなたは、AJAXを使用してファイルをアップロードする場合や、選択FormDataの代わりにJSON、または使用canvas.toBlobするのではなくtoDataURL
エンドレス

7
インラインとしてさらに良い:await (await fetch(imageDataURL)).blob()
icl7126

3
最新のブラウザをターゲットにしている場合は確認してください。ただし、そのためには、関数も非同期関数内にある必要があります。といえば... await fetch(url).then(r=>r.blob())ソーターです
Endless

2
非常にきちんとした解決策ですが、私の知識によれば、Access is denied.エラーが原因でIE(polyfill ofcを使用)では動作しません。fetchブロブのURLの下にブロブを公開していると思います-同様URL.createObjectUrlに-IE11では機能しません。リファレンス。多分IE11でフェッチを使用するためのいくつかの回避策がありますか?他の同期ソリューションよりもはるかに良く見えます:)
Papi

72

最適化された(ただし、読みにくい)実装:

function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    var sliceSize = 1024;
    var byteCharacters = atob(base64Data);
    var bytesLength = byteCharacters.length;
    var slicesCount = Math.ceil(bytesLength / sliceSize);
    var byteArrays = new Array(slicesCount);

    for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        var begin = sliceIndex * sliceSize;
        var end = Math.min(begin + sliceSize, bytesLength);

        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}

2
バイトをblobにスライスする理由はありますか?使用しない場合、不利な点やリスクはありますか?
Alfred Huang

Ionic 1 / Angular 1を搭載したAndroidで問題なく動作します。スライスが必要です。それ以外の場合は、OOM(Android 6.0.1)を実行します。
ユルゲン・'Kashban' Wahlmann

4
IE 11とChromeの両方のエンタープライズ環境で、あらゆる種類のドキュメントをシームレスに操作できる例はここにあります。
サントス2018

これは素晴らしいです。ありがとうございました!
elliotwesoff

説明が正しいでしょう。例えば、なぜそれはより高い性能を持っているのですか?
Peter Mortensen

19

すべてのブラウザーサポート、特にAndroidでは、これを追加できます。

try{
    blob = new Blob(byteArrays, {type : contentType});
}
catch(e){
    // TypeError old Google Chrome and Firefox
    window.BlobBuilder = window.BlobBuilder ||
                         window.WebKitBlobBuilder ||
                         window.MozBlobBuilder ||
                         window.MSBlobBuilder;
    if(e.name == 'TypeError' && window.BlobBuilder){
        var bb = new BlobBuilder();
        bb.append(byteArrays);
        blob = bb.getBlob(contentType);
    }
    else if(e.name == "InvalidStateError"){
        // InvalidStateError (tested on FF13 WinXP)
        blob = new Blob(byteArrays, {type : contentType});
    }
    else{
        // We're screwed, blob constructor unsupported entirely
    }
}

ありがとうございます。ただし、上で記述したコードスニペットに2つの問題があります。正しく読んだ場合:(1)最後のelse()のcatch()内のコードは、try()内の元のコードと同じです: "blob = new Blob(byteArrays、{type:contentType}) "...元の例外の後で同じコードを繰り返すことをお勧めする理由はわかりませんか?...(2)BlobBuilder.append()はバイト配列を受け入れることができませんが、ArrayBufferを受け入れることができます。したがって、このAPIを使用する前に、入力バイト配列をそのArrayBufferにさらに変換する必要があります。REF:developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher

14

画像データの場合、canvas.toBlob(非同期)を使用する方が簡単だと思います

function b64toBlob(b64, onsuccess, onerror) {
    var img = new Image();

    img.onerror = onerror;

    img.onload = function onload() {
        var canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;

        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

        canvas.toBlob(onsuccess);
    };

    img.src = b64;
}

var base64Data = 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQA...';
b64toBlob(base64Data,
    function(blob) {
        var url = window.URL.createObjectURL(blob);
        // do something with url
    }, function(error) {
        // handle error
    });

1
メタ情報のように、画像をpngに変換するようなものですので、同じ結果にはなりません。これは画像に対してのみ機能します
Endless

結果を同じタイプimage/jpgtoBlobするために、base64文字列からイメージタイプを抽出し、それを2番目のパラメーターとして関数に渡すことで、改善できると思います。それ以外は、これは完璧だと思います。30%のトラフィックとサーバー上のディスク領域(base64と比較して)を節約し、透明なPNGでもうまく機能します。
icl7126

1
2MBを超える画像で関数がクラッシュします... Androidでは例外が発生します:android.os.TransactionTooLarge
Ruben

14

この例を参照してください:https : //jsfiddle.net/pqhdce2L/

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }
    
  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}


var contentType = 'image/png';
var b64Data = Your Base64 encode;

var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);

var img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);


説明が正しいでしょう。
Peter Mortensen

9

Jeremyが提案したようにデータをスライスすると、Internet Explorer 11が非常に遅くなることに気づきました。これはChromeにも当てはまりますが、スライスされたデータをBlob-Constructorに渡すときにInternet Explorerに問題があるようです。私のマシンでは、5 MBのデータを渡すとInternet Explorerがクラッシュし、メモリ消費量が急激に増加しています。Chromeはすぐにブロブを作成します。

比較のためにこのコードを実行します。

var byteArrays = [],
    megaBytes = 2,
    byteArray = new Uint8Array(megaBytes*1024*1024),
    block,
    blobSlowOnIE, blobFastOnIE,
    i;

for (i = 0; i < (megaBytes*1024); i++) {
    block = new Uint8Array(1024);
    byteArrays.push(block);
}

//debugger;

console.profile("No Slices");
blobSlowOnIE = new Blob(byteArrays, { type: 'text/plain'});
console.profileEnd();

console.profile("Slices");
blobFastOnIE = new Blob([byteArray], { type: 'text/plain'});
console.profileEnd();

そこで、Jeremyによって記述された両方のメソッドを1つの関数に含めることにしました。クレジットはこれのために彼に行きます。

function base64toBlob(base64Data, contentType, sliceSize) {

    var byteCharacters,
        byteArray,
        byteNumbers,
        blobData,
        blob;

    contentType = contentType || '';

    byteCharacters = atob(base64Data);

    // Get BLOB data sliced or not
    blobData = sliceSize ? getBlobDataSliced() : getBlobDataAtOnce();

    blob = new Blob(blobData, { type: contentType });

    return blob;


    /*
     * Get BLOB data in one slice.
     * => Fast in Internet Explorer on new Blob(...)
     */
    function getBlobDataAtOnce() {
        byteNumbers = new Array(byteCharacters.length);

        for (var i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }

        byteArray = new Uint8Array(byteNumbers);

        return [byteArray];
    }

    /*
     * Get BLOB data in multiple slices.
     * => Slow in Internet Explorer on new Blob(...)
     */
    function getBlobDataSliced() {

        var slice,
            byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            slice = byteCharacters.slice(offset, offset + sliceSize);

            byteNumbers = new Array(slice.length);

            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            byteArray = new Uint8Array(byteNumbers);

            // Add slice
            byteArrays.push(byteArray);
        }

        return byteArrays;
    }
}

これを含めてくれてありがとう。IE11への最近の更新(2016年5月から2016年8月の間)により、配列からのBLOBの生成は、RAMの量が大幅に増え始めました。単一のUint8Arrayをブログコンストラクターに送信することにより、ほとんどRAMを使用せず、実際にプロセスを完了しました。
Andrew Vogel

テストサンプルのスライスサイズを1Kから8..16Kに増やすと、IEの時間が大幅に短縮されます。私のPCの元のコードでは5秒から8秒かかりました。8Kブロックのコードはわずか356msで、16Kブロックの場合は225msでした
Victor

5

プロジェクトに1つの依存関係を追加することに耐えられるなら、便利な機能を提供する素晴らしいblob-utilnpmパッケージがありますbase64StringToBlob。一度追加すると、package.json次のように使用できます。

import { base64StringToBlob } from 'blob-util';

const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

const blob = base64StringToBlob(b64Data, contentType);

// Do whatever you need with your blob...

5

私のようなコピーペースト愛好家のために、Chrome、Firefox、およびEdgeで動作する調理済みダウンロード機能を次に示します。

window.saveFile = function (bytesBase64, mimeType, fileName) {
var fileUrl = "data:" + mimeType + ";base64," + bytesBase64;
fetch(fileUrl)
    .then(response => response.blob())
    .then(blob => {
        var link = window.document.createElement("a");
        link.href = window.URL.createObjectURL(blob, { type: mimeType });
        link.download = fileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    });
}

createObjectURL第2引数...受け入れない
無限の

3

同期のBase64変換のより宣言的な方法を投稿しています。非同期fetch().blob()は非常にきちんとしていて、私はこのソリューションをとても気に入っていますが、ポリフィルを使用しても、Internet Explorer 11(おそらくEdge-私はこれをテストしていません)では動作しません-私のコメントをエンドレスに見てください'詳細については投稿してください。

const blobPdfFromBase64String = base64String => {
   const byteArray = Uint8Array.from(
     atob(base64String)
       .split('')
       .map(char => char.charCodeAt(0))
   );
  return new Blob([byteArray], { type: 'application/pdf' });
};

ボーナス

印刷したい場合は、次のようにします。

const isIE11 = !!(window.navigator && window.navigator.msSaveOrOpenBlob); // Or however you want to check it
const printPDF = blob => {
   try {
     isIE11
       ? window.navigator.msSaveOrOpenBlob(blob, 'documents.pdf')
       : printJS(URL.createObjectURL(blob)); // http://printjs.crabbly.com/
   } catch (e) {
     throw PDFError;
   }
};

ボーナスx 2-Internet Explorer 11の新しいタブでBLOBファイルを開く

サーバーでBase64文字列の前処理を行うことができる場合は、それをいくつかのURLの下で公開し、printJS:) のリンクを使用できます。


2

以下は、簡単にJavaScriptに変換でき、使用できるTypeScriptコードです。

/**
 * Convert BASE64 to BLOB
 * @param Base64Image Pass Base64 image data to convert into the BLOB
 */
private convertBase64ToBlob(Base64Image: any) {
    // Split into two parts
    const parts = Base64Image.split(';base64,');

    // Hold the content type
    const imageType = parts[0].split(':')[1];

    // Decode Base64 string
    const decodedData = window.atob(parts[1]);

    // Create UNIT8ARRAY of size same as row data length
    const uInt8Array = new Uint8Array(decodedData.length);

    // Insert all character code into uInt8Array
    for (let i = 0; i < decodedData.length; ++i) {
        uInt8Array[i] = decodedData.charCodeAt(i);
    }

    // Return BLOB image after conversion
    return new Blob([uInt8Array], { type: imageType });
}

4
このコードスニペットが解決策になる可能性がありますが、説明を含めると、投稿の品質を向上させるのに役立ちます。あなたは将来の読者のための質問に答えていることを覚えておいてください、そしてそれらの人々はあなたのコード提案の理由を知らないかもしれません。
ヨハン

2
また、なぜコメントで怒鳴るのですか?
canbax

4
あなたのTypescript codeコードは、単一のタイプがあり、その型がありますany。なぜ気になるの??
zoran404

0

フェッチ付きのメソッドが最善の解決策ですが、誰かがフェッチなしのメソッドを使用する必要がある場合は、前述の方法ではうまくいきませんでした。

function makeblob(dataURL) {
    const BASE64_MARKER = ';base64,';
    const parts = dataURL.split(BASE64_MARKER);
    const contentType = parts[0].split(':')[1];
    const raw = window.atob(parts[1]);
    const rawLength = raw.length;
    const uInt8Array = new Uint8Array(rawLength);

    for (let i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], { type: contentType });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.