Crear un BLOB a partir de una cadena Base64 en JavaScript

447

Tengo datos binarios codificados en Base64 en una cadena:

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

Me gustaría crear una blob:URL que contenga estos datos y mostrarla al usuario:

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

window.location = blobUrl;

No he podido descubrir cómo crear el BLOB.

En algunos casos, puedo evitar esto usando una data:URL:

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

window.location = dataUrl;

Sin embargo, en la mayoría de los casos, las data:URL son prohibitivamente grandes.


¿Cómo puedo decodificar una cadena Base64 en un objeto BLOB en JavaScript?

Jeremy Banks
fuente

Respuestas:

790

La atobfunción decodificará una cadena codificada en Base64 en una nueva cadena con un carácter para cada byte de los datos binarios.

const byteCharacters = atob(b64Data);

El punto de código de cada carácter (charCode) será el valor del byte. Podemos crear una matriz de valores de bytes aplicando esto usando el .charCodeAtmétodo para cada carácter en la cadena.

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

Puede convertir esta matriz de valores de bytes en una matriz de bytes tipificada real pasándola al Uint8Arrayconstructor.

const byteArray = new Uint8Array(byteNumbers);

Esto a su vez se puede convertir en un BLOB envolviéndolo en una matriz y pasándolo al Blobconstructor.

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

El código anterior funciona. Sin embargo, el rendimiento se puede mejorar un poco procesando los byteCharacterssegmentos más pequeños, en lugar de todos a la vez. En mi prueba aproximada, 512 bytes parece ser un buen tamaño de segmento. Esto nos da la siguiente función.

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;

Ejemplo completo:

Jeremy Banks
fuente
66
Hola jeremy Hemos tenido este código en nuestra aplicación web y no causó ningún problema hasta que los archivos que se descargaron eran más grandes. Por lo tanto, provocó bloqueos y bloqueos en el servidor de producción, cuando los usuarios usaban Chrome o IE para descargar archivos de más de 100 MB. Encontramos que la siguiente línea en IE estaba generando una excepción de memoria "var byteNumbers = new Array (slice.length)". Sin embargo, en Chrome, el bucle for causó el mismo problema. No pudimos encontrar una solución adecuada a este problema, luego pasamos a descargar directamente archivos usando window.open. ¿Puedes proporcionar algo de ayuda aquí?
Akshay Raut
¿Es ese algún método para convertir un archivo de video a base64 en react native? Logré hacerlo con un archivo de imagen, pero no encontré una solución para los videos. Los enlaces serán útiles o una solución también.
Diksha235
Entonces, ¿no hay problemas al almacenar 0 en la cadena devuelta por atob ()?
wcochran
esto no funcionó para mí para algunos blobs en Chrome y Firefox, pero funcionó al límite: /
Gragas entrante el
funcionó para mí arroja un error ** JSON Parse: toke no reconocido '<' ** verifiqué la cadena base64 al poner en un navegador que está creando una imagen. necesito ayuda.
Aman Deep
273

Aquí hay un método más mínimo sin dependencias o bibliotecas.
Requiere la nueva API de recuperación. ( ¿Puedo usarlo? )

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

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

Con este método también puede obtener fácilmente un ReadableStream, ArrayBuffer, texto y JSON.

Como una función:

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

Hice una prueba de rendimiento simple para la versión de sincronización ES6 de Jeremy.
La versión de sincronización bloqueará la interfaz de usuario por un tiempo. mantener abierto el devtool puede ralentizar el rendimiento de recuperación

Sin fin
fuente
1
¿Funcionará esto si el tamaño de la cadena codificada en base64 es grande, digamos más grande que 665536 caracteres, que es el límite para los tamaños de URI en Opera?
Daniel Kats
1
No sé, sé que puede ser un límite para la barra de direcciones, pero hacer cosas con AJAX podría ser una excepción, ya que no tiene que ser renderizado. Tienes que probarlo. Si fuera donde yo nunca habría obtenido la cadena base64 en primer lugar. Pensar que es una mala práctica, requiere más memoria y tiempo para decodificar y codificar. createObjectURLen lugar de readAsDataURLes mucho mejor, por ejemplo. Y si sube archivos usando ajax, elija en FormDatalugar de JSON, o use en canvas.toBloblugar detoDataURL
Endless
77
Aún mejor como en línea:await (await fetch(imageDataURL)).blob()
icl7126
3
claro, si te diriges al último navegador. Pero eso requiere que la función esté dentro de una función asíncrona también. Hablando de ... await fetch(url).then(r=>r.blob())es clasificador
Endless
2
Solución muy ordenada, pero según mi conocimiento no funcionará con IE (con polyfill ofc) debido a un Access is denied.error. Supongo que fetchexpone blob bajo blob url, de la misma manera URL.createObjectUrl, que no funcionará en ie11. referencia . ¿Quizás haya alguna solución alternativa para usar fetch con IE11? Se ve mucho mejor que otras soluciones de sincronización :)
Papi
72

Implementación optimizada (pero menos legible):

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
fuente
2
¿Hay alguna razón para cortar los bytes en blobs? Si no lo uso, ¿hay alguna desventaja o riesgo?
Alfred Huang
Funciona muy bien en Android con Ionic 1 / Angular 1. Se requiere Slice, de lo contrario me encuentro con OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann
44
El único ejemplo por ahí que pude trabajar sin problemas con cualquier tipo de documento en un entorno empresarial tanto en IE 11 como en Chrome.
santos
Esto es fantástico. ¡Gracias!
elliotwesoff
Una explicación estaría en orden. Por ejemplo, ¿por qué tiene un mayor rendimiento?
Peter Mortensen
19

Para todo el soporte del navegador, especialmente en Android, quizás pueda agregar esto:

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
fuente
Gracias, pero hay DOS problemas en el fragmento de código que escribió anteriormente si lo leí correctamente: (1) El código dentro de catch () en el último else-if es el mismo que el código original dentro de try (): "blob = Blob nuevo (byteArrays, {type: contentType}) "... No sé por qué sugiere repetir el mismo código después de la excepción original. ... (2) BlobBuilder.append () NO puede aceptar matrices de bytes sino ArrayBuffer. Por lo tanto, las matrices de bytes de entrada deben convertirse más en su ArrayBuffer antes de usar esta API. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher
14

Para los datos de imagen, me resulta más fácil de usar canvas.toBlob(asíncrono)

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
fuente
1
Supongo que pierdes información con eso ... como la metainformación, es como convertir cualquier imagen a png, por lo que no es el mismo resultado, también esto solo funciona para imágenes
Endless
Supongo que podría mejorarlo extrayendo el tipo de imagen image/jpgde la cadena base64 y luego pasarlo como un segundo parámetro a la toBlobfunción para que el resultado sea del mismo tipo. Aparte de eso, creo que esto es perfecto: ahorra el 30% del tráfico y el espacio en disco en el servidor (en comparación con base64) y funciona bien incluso con PNG transparente.
icl7126
1
La función se bloquea con imágenes de más de 2 MB ... en Android obtengo la excepción: android.os.TransactionTooLarge
Ruben
14

Vea este ejemplo: 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
fuente
Una explicación estaría en orden.
Peter Mortensen
9

Noté que Internet Explorer 11 se vuelve increíblemente lento al cortar los datos como sugirió Jeremy. Esto es cierto para Chrome, pero Internet Explorer parece tener un problema al pasar los datos divididos al Blob-Constructor. En mi máquina, pasar 5 MB de datos hace que Internet Explorer falle y el consumo de memoria se está yendo por las nubes. Chrome crea la burbuja en poco tiempo.

Ejecute este código para una comparación:

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

Así que decidí incluir ambos métodos descritos por Jeremy en una función. Los créditos van a él por esto.

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
fuente
Gracias por incluir esto. Con una actualización reciente de IE11 (entre 5/2016 y 8/2016), la generación de blobs a partir de matrices comenzó a tomar una cantidad mayor de ram. Al enviar un único Uint8Array al constructor del blog, casi no utilizó ram y en realidad completó el proceso.
Andrew Vogel
El aumento del tamaño de corte en la muestra de prueba de 1K a 8..16K disminuye significativamente el tiempo en IE. En mi PC, el código original tardó de 5 a 8 segundos, el código con bloques de 8K tomó solo 356 ms y 225 ms para bloques de 16K
Victor
6

Para todos los amantes de copiar y pegar como yo, aquí hay una función de descarga que funciona en Chrome, Firefox y 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);
    });
}
Eyup Yusein
fuente
el createObjectURLno acepta un segundo argumento ...
Infinito
5

Si puede soportar agregar una dependencia a su proyecto, está el excelente blob-utilpaquete npm que proporciona una base64StringToBlobfunción práctica . Una vez agregado a tu package.jsonpuedes usarlo así:

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
fuente
3

Estoy publicando una forma más declarativa de sincronización de conversión de Base64. Si bien la sincronización asíncrona fetch().blob()es muy ordenada y me gusta mucho esta solución, no funciona en Internet Explorer 11 (y probablemente Edge, no lo he probado), incluso con el polyfill, mira mi comentario a Endless ' Publicar para más detalles.

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

Prima

Si desea imprimirlo, puede hacer algo como:

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: abrir un archivo BLOB en una nueva pestaña para Internet Explorer 11

Si puede hacer un preprocesamiento de la cadena Base64 en el servidor, puede exponerla bajo alguna URL y usar el enlace en printJS:)

Papi
fuente
2

El siguiente es mi código TypeScript que se puede convertir fácilmente en JavaScript y puede usar

/**
 * 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
fuente
44
Si bien este fragmento de código puede ser la solución, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código.
Johan
2
Además, ¿por qué gritas a los comentarios?
canbax
44
Su Typescript codecódigo solo tiene un tipo SINGLE y ese tipo es any. ¿Por qué molestarse?
zoran404
0

El método con fetch es la mejor solución, pero si alguien necesita usar un método sin fetch, aquí está, ya que los mencionados anteriormente no funcionaron para mí:

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
fuente