Erstellen eines BLOBs aus einer Base64-Zeichenfolge in JavaScript

447

Ich habe Base64-codierte Binärdaten in einer Zeichenfolge:

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

Ich möchte eine blob:URL erstellen, die diese Daten enthält, und sie dem Benutzer anzeigen:

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

window.location = blobUrl;

Ich konnte nicht herausfinden, wie das BLOB erstellt wird.

In einigen Fällen kann ich dies vermeiden, indem ich data:stattdessen eine URL verwende:

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

window.location = dataUrl;

In den meisten Fällen sind die data:URLs jedoch unerschwinglich groß.


Wie kann ich eine Base64-Zeichenfolge in ein BLOB-Objekt in JavaScript dekodieren?

Jeremy Banks
quelle

Antworten:

789

Die atobFunktion decodiert eine Base64-codierte Zeichenfolge in eine neue Zeichenfolge mit einem Zeichen für jedes Byte der Binärdaten.

const byteCharacters = atob(b64Data);

Der Codepunkt jedes Zeichens (charCode) ist der Wert des Bytes. Wir können ein Array von Bytewerten erstellen, indem wir dies mit der .charCodeAtMethode für jedes Zeichen in der Zeichenfolge anwenden .

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

Sie können dieses Array von Bytewerten in ein real typisiertes Byte-Array konvertieren, indem Sie es an den Uint8ArrayKonstruktor übergeben.

const byteArray = new Uint8Array(byteNumbers);

Dies kann wiederum in ein BLOB konvertiert werden, indem es in ein Array eingeschlossen und an den BlobKonstruktor übergeben wird.

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

Der obige Code funktioniert. Die Leistung kann jedoch ein wenig verbessert werden, indem die byteCharactersin kleineren Scheiben anstatt auf einmal verarbeitet werden. In meinem groben Test scheinen 512 Bytes eine gute Slice-Größe zu sein. Dies gibt uns die folgende Funktion.

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;

Vollständiges Beispiel:

Jeremy Banks
quelle
6
Hallo Jeremy. Wir hatten diesen Code in unserer Webanwendung und er verursachte keine Probleme, bis die heruntergeladenen Dateien größer waren. Dies führte zu Hängen und Abstürzen auf dem Produktionsserver, wenn Benutzer Chrome oder IE zum Herunterladen von Dateien mit mehr als 100 MB verwendeten. Wir haben festgestellt, dass die folgende Zeile im IE die Speicherausnahme "var byteNumbers = new Array (Slice.length)" auslöste. In Chrome war es jedoch die for-Schleife, die das gleiche Problem verursachte. Wir konnten keine richtige Lösung für dieses Problem finden und haben dann Dateien direkt über window.open heruntergeladen. Können Sie hier etwas helfen?
Akshay Raut
Ist das eine Methode, um eine Videodatei in React Native in base64 zu konvertieren? Ich habe es mit einer Bilddatei geschafft, aber keine Lösung für Videos gefunden. Links sind hilfreich oder auch eine Lösung.
Diksha235
Es gibt also keine Probleme beim Speichern von Nullen in der von atob () zurückgegebenen Zeichenfolge?
Wcochran
Dies funktionierte nicht für mich für einige Blobs auf Chrome und Firefox, sondern für Edge: /
Gragas Incoming
hat für mich gearbeitet. Es wird ein ** JSON-Analysefehler ausgelöst: Nicht erkanntes Token '<' ** Ich habe die Base64-Zeichenfolge überprüft, indem ich einen Browser eingefügt habe, in dem ein Bild erstellt wird. Hilfe benötigen.
Aman Deep
272

Hier ist eine minimalere Methode ohne Abhängigkeiten oder Bibliotheken.
Es erfordert die neue Abruf-API. ( Kann ich es benutzen? )

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

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

Mit dieser Methode können Sie auch problemlos einen ReadableStream, ArrayBuffer, Text und JSON abrufen.

Als eine Funktion:

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

Ich habe einen einfachen Leistungstest für Jeremys ES6-Synchronisierungsversion durchgeführt.
Die Synchronisierungsversion blockiert die Benutzeroberfläche für eine Weile. Wenn Sie das devtool offen halten, kann dies die Abrufleistung verlangsamen

Endlos
quelle
1
Funktioniert dies immer noch, wenn die Größe der Base64-codierten Zeichenfolge groß ist, beispielsweise größer als 665536 Zeichen. Dies ist die Grenze für URI-Größen in Opera?
Daniel Kats
1
Ich weiß nicht, ich weiß, dass es eine Grenze für die Adressleiste sein kann, aber Dinge mit AJAX zu tun, könnte eine Ausnahme sein, da es nicht gerendert werden muss. Du musst es testen. Wenn ich dort wäre, hätte ich den base64-String überhaupt nicht bekommen. Das Denken, dass es eine schlechte Praxis ist, nimmt mehr Speicher und Zeit zum Dekodieren und Kodieren in Anspruch. createObjectURLstatt readAsDataURList zum Beispiel viel besser. Und wenn Sie Dateien mit Ajax hochladen, wählen Sie FormDataanstelle von JSONoder canvas.toBlobanstelle vontoDataURL
Endless
7
Noch besser als Inline:await (await fetch(imageDataURL)).blob()
icl7126
3
Sicher, wenn Sie auf den neuesten Browser abzielen. Dazu muss sich die Funktion jedoch auch in einer asynchronen Funktion befinden. Apropos ... await fetch(url).then(r=>r.blob())ist Sortierer
Endless
2
Sehr nette Lösung, aber meines Wissens funktioniert sie aufgrund eines Access is denied.Fehlers nicht mit IE (mit Polyfill ofc) . Ich denke, fetchmacht Blob unter Blob-URL URL.createObjectUrlverfügbar - auf die gleiche Weise - was bei ie11 nicht funktioniert. Referenz . Vielleicht gibt es eine Problemumgehung, um Fetch mit IE11 zu verwenden? Es sieht viel besser aus als andere Synchronisierungslösungen :)
Papi
72

Optimierte (aber weniger lesbare) Implementierung:

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 });
}
Bacher
quelle
2
Gibt es einen Grund, die Bytes in Blobs zu schneiden? Wenn ich nicht benutze, gibt es einen Nachteil oder ein Risiko?
Alfred Huang
Funktioniert hervorragend auf Android mit Ionic 1 / Angular 1. Slice ist erforderlich, sonst stoße ich auf OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann
4
Einziges Beispiel da draußen Ich konnte nahtlos mit jedem Dokumenttyp in einer Unternehmensumgebung in IE 11 und Chrome arbeiten.
Santos
Das ist fantastisch. Vielen Dank!
Elliotwesoff
Eine Erklärung wäre angebracht. ZB warum hat es eine höhere Leistung?
Peter Mortensen
19

Für die gesamte Browserunterstützung, insbesondere für Android, können Sie möglicherweise Folgendes hinzufügen:

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
    }
}
Jayce Lin
quelle
Danke, aber es gibt ZWEI Probleme mit dem Code-Snippet, das Sie oben geschrieben haben, wenn ich es richtig gelesen habe: (1) Der Code in catch () auf dem letzten else-if ist der gleiche wie der ursprüngliche Code in try (): "blob = new Blob (byteArrays, {type: contentType}) "... Ich weiß nicht, warum Sie vorschlagen, denselben Code nach der ursprünglichen Ausnahme zu wiederholen? ... (2) BlobBuilder.append () kann KEINE Bytes-Arrays akzeptieren, sondern ArrayBuffer. Daher müssen die Eingabe-Byte-Arrays vor Verwendung dieser API weiter in ihren ArrayBuffer konvertiert werden. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher
14

Für Bilddaten finde ich es einfacher zu verwenden canvas.toBlob(asynchron)

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
    });
amirnissim
quelle
1
Ich denke, Sie verlieren einige Informationen damit ... wie die Meta-Informationen ist es wie das Konvertieren eines Bildes in PNG, so dass es nicht das gleiche Ergebnis ist, auch dies funktioniert nur für Bilder
Endless
Ich denke, Sie könnten es verbessern, indem Sie den Bildtyp image/jpgaus der base64-Zeichenfolge extrahieren und ihn dann als zweiten Parameter an toBlobfunction übergeben, sodass das Ergebnis vom gleichen Typ ist. Abgesehen davon halte ich dies für perfekt - es spart 30% des Datenverkehrs und Ihren Speicherplatz auf dem Server (im Vergleich zu base64) und funktioniert auch mit transparentem PNG gut.
icl7126
1
Die Funktion stürzt mit Bildern ab, die größer als 2 MB sind ... in Android bekomme ich die Ausnahme: android.os.TransactionTooLarge
Ruben
14

Siehe dieses Beispiel: 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);

Arcaela
quelle
Eine Erklärung wäre angebracht.
Peter Mortensen
9

Mir ist aufgefallen, dass Internet Explorer 11 beim Schneiden der Daten unglaublich langsam wird, wie Jeremy vorgeschlagen hat. Dies gilt für Chrome, aber Internet Explorer scheint ein Problem zu haben, wenn die geschnittenen Daten an den Blob-Constructor übergeben werden. Wenn auf meinem Computer 5 MB Daten übergeben werden, stürzt der Internet Explorer ab und der Speicherverbrauch steigt. Chrome erstellt den Blob in kürzester Zeit.

Führen Sie diesen Code zum Vergleich aus:

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

Deshalb habe ich beschlossen, beide von Jeremy beschriebenen Methoden in eine Funktion aufzunehmen. Dafür gehen ihm Credits.

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;
    }
}
Martinoss
quelle
Vielen Dank, dass Sie dies aufgenommen haben. Mit einem kürzlich durchgeführten Update auf IE11 (zwischen 5/2016 und 8/2016) wurde für das Generieren von Blobs aus Arrays eine größere Menge an RAM benötigt. Durch das Senden eines einzelnen Uint8Array an den Blog-Konstruktor wurde fast kein RAM verwendet und der Vorgang tatsächlich abgeschlossen.
Andrew Vogel
Durch Erhöhen der Schnittgröße in der Testprobe von 1 KB auf 8,16 KB wird die Zeit im IE signifikant verkürzt. Auf meinem PC dauerte der ursprüngliche Code 5 bis 8 Sekunden, Code mit 8K-Blöcken nur 356 ms und 225 ms für 16K-Blöcke
Victor
5

Wenn Sie es ertragen können, Ihrem Projekt eine Abhängigkeit hinzuzufügen, gibt es das großartige blob-utilnpm-Paket , das eine praktische base64StringToBlobFunktion bietet . Einmal zu Ihrem hinzugefügt, können package.jsonSie es wie folgt verwenden:

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...
gabriele.genta
quelle
5

Für alle Liebhaber von Copy-Paste wie mich gibt es hier eine gekochte Download-Funktion, die auf Chrome, Firefox und Edge funktioniert:

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);
    });
}
Eyup Yusein
quelle
die createObjectURLakzeptieren kein 2. Argument ...
Endless
3

Ich veröffentliche eine deklarativere Methode zur Synchronisierung der Base64-Konvertierung. Async fetch().blob()ist zwar sehr ordentlich und ich mag diese Lösung sehr, aber sie funktioniert nicht mit Internet Explorer 11 (und wahrscheinlich Edge - ich habe diese nicht getestet), selbst mit der Polyfüllung - werfen Sie einen Blick auf meinen Kommentar zu Endless ' Post für weitere Details.

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

Bonus

Wenn Sie es drucken möchten, können Sie Folgendes tun:

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

Bonus x 2 - Öffnen einer BLOB-Datei in einem neuen Tab für Internet Explorer 11

Wenn Sie in der Lage sind, die Base64-Zeichenfolge auf dem Server vorzuverarbeiten, können Sie sie unter einer URL verfügbar machen und den Link in printJS:) verwenden

Papi
quelle
2

Es folgt mein TypeScript-Code, der einfach in JavaScript konvertiert werden kann und den Sie verwenden können

/**
 * 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 });
}
KAUSHIK PARMAR
quelle
4
Während dieses Code-Snippet die Lösung sein kann, hilft das Hinzufügen einer Erklärung wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Johan
2
Warum schreist du auch bei Kommentaren?
Canbax
4
Ihr Typescript codeCode hat nur einen SINGLE-Typ und dieser Typ ist any. Wie, warum überhaupt stören?
zoran404
0

Die Methode mit Fetch ist die beste Lösung, aber wenn jemand eine Methode ohne Fetch verwenden muss, dann ist sie hier, da die zuvor genannten für mich nicht funktioniert haben:

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 });
}
Akshay
quelle