Creazione di un BLOB da una stringa Base64 in JavaScript


447

Ho dati binari codificati Base64 in una stringa:

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

Vorrei creare un blob:URL contenente questi dati e mostrarlo all'utente:

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

window.location = blobUrl;

Non sono stato in grado di capire come creare il BLOB.

In alcuni casi sono in grado di evitarlo utilizzando data:invece un URL:

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

window.location = dataUrl;

Tuttavia, nella maggior parte dei casi gli data:URL sono proibizionalmente grandi.


Come posso decodificare una stringa Base64 in un oggetto BLOB in JavaScript?

Risposte:


789

La atobfunzione decodificherà una stringa codificata Base64 in una nuova stringa con un carattere per ogni byte dei dati binari.

const byteCharacters = atob(b64Data);

Il punto di codice di ciascun carattere (charCode) sarà il valore del byte. Siamo in grado di creare una matrice di valori byte applicando questo utilizzando il .charCodeAtmetodo per ciascun carattere nella stringa.

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

È possibile convertire questo array di valori di byte in un array di byte tipizzato reale passandolo al Uint8Arraycostruttore.

const byteArray = new Uint8Array(byteNumbers);

Questo a sua volta può essere convertito in un BLOB avvolgendolo in un array e passandolo al Blobcostruttore.

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

Il codice sopra funziona. Tuttavia, le prestazioni possono essere leggermente migliorate elaborando le byteCharacterssezioni più piccole, piuttosto che tutte in una volta. Nel mio test approssimativo 512 byte sembrano essere una buona dimensione della fetta. Questo ci dà la seguente funzione.

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;

Esempio completo:


6
Ciao Jeremy. Abbiamo avuto questo codice nella nostra applicazione web e non ha causato alcun problema fino a quando i file scaricati non sono stati di dimensioni maggiori. Quindi ha causato blocchi e arresti anomali nel server di produzione, quando gli utenti utilizzavano Chrome o IE per scaricare file di dimensioni superiori a 100 MB. Abbiamo scoperto che la seguente riga in IE generava un'eccezione di memoria "var byteNumbers = new Array (slice.length)". Tuttavia in Chrome, era il ciclo for che causava lo stesso problema. Non siamo riusciti a trovare una soluzione adeguata a questo problema, quindi siamo passati al download diretto dei file utilizzando window.open. Potete fornire un aiuto qui?
Akshay Raut,

È un metodo per convertire un file video in base64 in reattivo nativo? Sono riuscito a farlo con un file di immagine ma non ho trovato una soluzione per lo stesso per i video. I collegamenti saranno utili o anche una soluzione.
Diksha235,

Quindi non ci sono problemi nella memorizzazione di 0 nella stringa restituita da atob ()?
wcochran,

questo non ha funzionato per me per alcuni BLOB su Chrome e Firefox, ma ha funzionato al limite: /
Gragas Incoming

ha funzionato per me. genera un errore ** JSON Parse: toke non riconosciuto '<' ** ho controllato la stringa base64 inserendo un browser che sta creando un'immagine. bisogno di aiuto.
Aman Deep,

272

Ecco un metodo più minimale senza dipendenze o librerie.
Richiede la nuova API di recupero. ( Posso usarlo? )

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

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

Con questo metodo puoi anche ottenere facilmente ReadableStream, ArrayBuffer, testo e JSON.

Come una funzione:

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

Ho fatto un semplice test delle prestazioni verso la versione di sincronizzazione ES6 di Jeremy.
La versione di sincronizzazione bloccherà l'interfaccia utente per un po '. tenere aperto devtool può rallentare le prestazioni di recupero


1
Funzionerà comunque se la dimensione della stringa con codifica base64 è grande, diciamo più grande di 665536 caratteri, che è il limite per le dimensioni dell'URI in Opera?
Daniel Kats,

1
Non lo so, so che può essere un limite per la barra degli indirizzi, ma fare cose con AJAX potrebbe essere un'eccezione poiché non deve essere eseguito il rendering. Devi provarlo. Se fosse lì, non avrei mai ottenuto la stringa base64 in primo luogo. Pensando che sia una cattiva pratica, occupa più memoria e tempo per decodificare e codificare. createObjectURLinvece di readAsDataURLè molto meglio per esempio. E se carichi file utilizzando ajax, scegli FormDatainvece di JSON, oppure usa canvas.toBlobinvece ditoDataURL
Endless

7
Ancora meglio come in linea:await (await fetch(imageDataURL)).blob()
icl7126

3
certo, se scegli come target l'ultimo browser. Ma ciò richiede che la funzione sia anche all'interno di una funzione asincrona. Parlare di ... await fetch(url).then(r=>r.blob())è sorter
Endless

2
Soluzione molto accurata, ma secondo la mia conoscenza non funzionerà con IE (con polyfill ofc) a causa di un Access is denied.errore. Immagino che fetchesponga BLOB sotto BLOB URL - allo stesso modo URL.createObjectUrl- che non funzionerà su ie11. riferimento . Forse c'è qualche soluzione alternativa per usare fetch con IE11? Sembra molto meglio di altre soluzioni di sincronizzazione :)
Papi

72

Implementazione ottimizzata (ma meno leggibile):

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
C'è qualche motivo per dividere i byte in BLOB? Se non uso, c'è qualche svantaggio o rischio?
Alfred Huang,

Funziona alla grande su Android con Ionic 1 / Angular 1. È necessario Slice altrimenti corro in OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann

4
Solo un esempio là fuori ho potuto lavorare senza problemi con qualsiasi tipo di documento in un ambiente aziendale in IE 11 e Chrome.
santos,

È fantastico. Grazie!
elliotwesoff,

Una spiegazione sarebbe in ordine. Ad esempio, perché ha prestazioni più elevate?
Peter Mortensen,

19

Per tutto il supporto del browser, specialmente su Android, forse puoi aggiungere questo:

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

Grazie, ma ci sono DUE problemi sullo snippet di codice che hai scritto sopra se l'ho letto correttamente: (1) Il codice in catch () sull'ultimo altro -se è lo stesso del codice originale in try (): "blob = new Blob (byteArrays, {type: contentType}) "... Non so perché suggerisci di ripetere lo stesso codice dopo l'eccezione originale? ... (2) BlobBuilder.append () NON può accettare byte-array ma ArrayBuffer. Pertanto, i byte di array di input devono essere ulteriormente convertiti nel suo ArrayBuffer prima di utilizzare questa API. RIF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher

14

Per i dati di immagine, trovo più semplice da usare canvas.toBlob(asincrono)

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
Suppongo che perdi alcune informazioni con quello ... come le meta info è come convertire qualsiasi immagine in png, quindi non è lo stesso risultato, anche questo funziona solo per le immagini
Endless

Suppongo che potresti migliorarlo estraendo il tipo di immagine image/jpgdalla stringa base64 e quindi passandolo come secondo parametro in toBlobfunzione in modo che il risultato sia lo stesso tipo. A parte questo, penso che sia perfetto: consente di risparmiare il 30% del traffico e lo spazio su disco sul server (rispetto a base64) e funziona bene anche con PNG trasparente.
icl7126,

1
La funzione si arresta in modo anomalo con immagini di dimensioni superiori a 2 MB ... in Android ottengo l'eccezione: android.os.TransactionTooLarge
Ruben

14

Vedi questo esempio: 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);


Una spiegazione sarebbe in ordine.
Peter Mortensen,

9

Ho notato che Internet Explorer 11 diventa incredibilmente lento quando taglia i dati come suggerito da Jeremy. Questo è vero per Chrome, ma Internet Explorer sembra avere un problema quando si passano i dati suddivisi al BLOB-Costruttore. Sulla mia macchina, il passaggio di 5 MB di dati provoca l'arresto anomalo di Internet Explorer e il consumo di memoria sta salendo alle stelle. Chrome crea il BLOB in pochissimo tempo.

Esegui questo codice per un confronto:

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

Così ho deciso di includere entrambi i metodi descritti da Jeremy in una funzione. I crediti vanno a lui per questo.

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

Grazie per aver incluso questo Con un recente aggiornamento a IE11 (tra il 5/2016 e l'8/2016), la generazione di BLOB da array ha iniziato a richiedere una quantità maggiore di RAM. Inviando un singolo Uint8Array nel costruttore del blog, non ha usato quasi ram e ha effettivamente completato il processo.
Andrew Vogel,

L'aumento della dimensione della fetta nel campione da 1K a 8..16K riduce significativamente il tempo in IE. Sul mio PC il codice originale impiegava da 5 a 8 secondi, il codice con blocchi da 8K impiegava solo 356ms e 225ms per blocchi da 16K
Victor

5

Se riesci ad aggiungere una dipendenza al tuo progetto, c'è il fantastico blob-utilpacchetto npm che offre una comoda base64StringToBlobfunzione. Una volta aggiunto al tuo package.jsonpuoi usarlo in questo modo:

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

Per tutti gli appassionati di copia-incolla come me, ecco una funzione di download cucinata che funziona su Chrome, Firefox e 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);
    });
}

il createObjectURLnon accettano un secondo argomento ...
Endless

3

Sto pubblicando un modo più dichiarativo per sincronizzare la conversione Base64. Mentre async fetch().blob()è molto pulito e mi piace molto questa soluzione, non funziona su Internet Explorer 11 (e probabilmente Edge - non l'ho provato), anche con il polyfill - dai un'occhiata al mio commento a Endless ' post per maggiori dettagli.

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

indennità

Se vuoi stamparlo potresti fare qualcosa del tipo:

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: apertura di un file BLOB in una nuova scheda per Internet Explorer 11

Se sei in grado di eseguire una preelaborazione della stringa Base64 sul server, puoi esporla in alcuni URL e utilizzare il link in printJS:)


2

Di seguito è riportato il mio codice TypeScript che può essere convertito facilmente in JavaScript e che è possibile utilizzare

/**
 * 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
Sebbene questo frammento di codice possa essere la soluzione, includere una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e che queste persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
Johan

2
Inoltre, perché stai urlando ai commenti?
canbax,

4
Il tuo Typescript codecodice ha solo un tipo SINGOLO e quel tipo è any. Mi piace perché nemmeno preoccuparsi ??
zoran404,

0

Il metodo con fetch è la soluzione migliore, ma se qualcuno ha bisogno di usare un metodo senza fetch, eccolo qui, poiché quelli menzionati in precedenza non funzionavano per me:

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 });
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.