Criando um BLOB a partir de uma string Base64 em JavaScript

447

Eu tenho dados binários codificados em Base64 em uma string:

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

Eu gostaria de criar uma blob:URL contendo esses dados e exibi-los para o usuário:

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

window.location = blobUrl;

Não consegui descobrir como criar o BLOB.

Em alguns casos, sou capaz de evitar isso usando uma data:URL:

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

window.location = dataUrl;

No entanto, na maioria dos casos, os data:URLs são proibitivamente grandes.


Como decodificar uma string Base64 para um objeto BLOB em JavaScript?

Jeremy Banks
fonte

Respostas:

789

A atobfunção decodificará uma string codificada em Base64 em uma nova string com um caractere para cada byte dos dados binários.

const byteCharacters = atob(b64Data);

O ponto de código de cada caractere (charCode) será o valor do byte. Podemos criar uma matriz de valores de bytes, aplicando isso usando o .charCodeAtmétodo para cada caractere na string.

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

Você pode converter essa matriz de valores de bytes em uma matriz de bytes digitada real passando-a para o Uint8Arrayconstrutor.

const byteArray = new Uint8Array(byteNumbers);

Por sua vez, isso pode ser convertido em um BLOB envolvendo-o em uma matriz e passando-o para o Blobconstrutor.

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

O código acima funciona. No entanto, o desempenho pode ser melhorado um pouco ao processar as byteCharactersfatias menores, em vez de todas de uma vez. No meu teste aproximado, 512 bytes parecem ser um bom tamanho de fatia. Isso nos dá a seguinte função.

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;

Exemplo completo:

Jeremy Banks
fonte
6
Oi Jeremy. Tivemos esse código em nosso aplicativo da web e não causou nenhum problema até que os arquivos baixados fossem maiores. Por isso, causou travamentos e falhas no servidor de produção, quando os usuários estavam usando o Chrome ou IE para baixar arquivos maiores que 100mb. Descobrimos que a seguinte linha no IE estava aumentando a exceção de memória "var byteNumbers = new Array (slice.length)". No entanto, no chrome, era o loop for causando o mesmo problema. Não foi possível encontrar uma solução adequada para esse problema, depois passamos para o download direto de arquivos usando o window.open. Você pode fornecer alguma ajuda aqui?
Akshay Raut
Existe algum método para converter um arquivo de vídeo em base64 em reagir nativo? Consegui fazer isso com um arquivo de imagem, mas não encontrei uma solução para o mesmo para vídeos. Os links serão úteis ou também uma solução.
Diksha235
Portanto, não há problemas para armazenar zeros na string retornada por atob ()?
Wcochran # 6/19
isso não funcionou para mim em alguns blobs no Chrome e Firefox, mas funcionou no limite: /
Gragas Ingressando em 25/06/19
funcionou para mim. emite um erro ** JSON Parse: token não reconhecido '<' ** verifiquei a string base64 colocando no navegador que está criando uma imagem. Precisa de alguma ajuda.
Aman Profundo
272

Aqui está um método mais minimalista, sem dependências ou bibliotecas.
Requer a nova API de busca. ( Posso usá-lo? )

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

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

Com esse método, você também pode obter facilmente um ReadableStream, ArrayBuffer, texto e JSON.

Como uma função:

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

Fiz um teste de desempenho simples para a versão de sincronização do Jeremy ES6.
A versão de sincronização bloqueará a interface do usuário por um tempo. manter o devtool aberto pode diminuir o desempenho da busca

Sem fim
fonte
1
Isso ainda funcionará se o tamanho da string codificada em base64 for grande, digamos, com mais de 665536 caracteres, que é o limite para os tamanhos de URI no Opera?
Daniel Kats
1
Não sei, sei que pode ser um limite para a barra de endereços, mas fazer coisas com o AJAX pode ser uma exceção, pois não precisa ser renderizado. Você tem que testar. Se fosse onde eu nunca teria conseguido a string base64 em primeiro lugar. Pensando que é uma má prática, consome mais memória e tempo para decodificar e codificar. createObjectURLem vez de readAsDataURLé muito melhor, por exemplo. E se você fazer upload de arquivos usando Ajax, escolha FormData, em vez de JSON, ou usar canvas.toBlob, em vez detoDataURL
Sem Fim
7
Ainda melhor como em linha:await (await fetch(imageDataURL)).blob()
icl7126 10/10
3
com certeza, se você segmentar o navegador mais recente. Mas isso requer que a função também esteja dentro de uma função assíncrona. Falando de ... await fetch(url).then(r=>r.blob())é classificador
Sem Fim
2
Solução muito elegante, mas de acordo com o meu conhecimento não funcionará com o IE (com polyfill ofc) devido a Access is denied.erro. Eu acho que fetchexpõe blob sob url de blob - da mesma maneira URL.createObjectUrlque - que não funcionará no ie11. referência . Talvez haja alguma solução alternativa para usar a busca com o IE11? Parece muito melhor do que outras soluções de sincronização :)
Papi
72

Implementação otimizada (mas menos legível):

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
fonte
2
Existe alguma razão para dividir os bytes em blobs? Se eu não usar, há alguma desvantagem ou risco?
Alfred Huang
Funciona muito bem no Android com Ionic 1 / Angular 1. Fatia é necessária, caso contrário eu corro para o OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann 3/03
4
Único exemplo lá fora, eu poderia trabalhar perfeitamente com qualquer tipo de documento em um ambiente corporativo no IE 11 e no Chrome.
22718 santos
Isto é fantástico. Obrigado!
elliotwesoff 5/09/19
Uma explicação estaria em ordem. Por exemplo, por que ele tem melhor desempenho?
Peter Mortensen
19

Para todo o suporte ao navegador, especialmente no Android, talvez você possa adicionar o seguinte:

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
fonte
Obrigado, mas existem DOIS problemas no trecho de código que você escreveu acima, se eu o leio corretamente: (1) O código dentro de catch () no último item, se é o mesmo que o código original em try (): "blob = novo Blob (byteArrays, {type: contentType}) "" Não sei por que você sugere repetir o mesmo código após a exceção original? ... (2) BlobBuilder.append () NÃO pode aceitar bytes-matrizes, mas ArrayBuffer. Portanto, os bytes-matrizes de entrada devem ser convertidos ainda mais em seu ArrayBuffer antes de usar esta API. REF: developer.mozilla.org/pt-BR/docs/Web/API/BlobBuilder
Panini Luncher
14

Para dados de imagem, acho mais simples de usar canvas.toBlob(assí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
fonte
1
Eu acho que você perder algumas informações com isso ... como a informação meta é como converter qualquer imagem para png, por isso não é o mesmo resultado, também isso só funciona para imagens
Infinitas
Eu acho que você poderia melhorá-lo extraindo o tipo de imagem image/jpgda string base64 e depois passá-lo como um segundo parâmetro para a toBlobfunção, para que o resultado seja o mesmo tipo. Fora isso, acho que isso é perfeito - economiza 30% do tráfego e do espaço em disco no servidor (comparado ao base64) e funciona bem mesmo com PNG transparente.
icl7126
1
A função de falhas com imagens maiores do que 2MB ... no Android recebo a exceção: android.os.TransactionTooLarge
Ruben
14

Veja este exemplo: 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
fonte
Uma explicação estaria em ordem.
Peter Mortensen
9

Percebi que o Internet Explorer 11 fica incrivelmente lento ao fatiar os dados, como sugeriu Jeremy. Isso ocorre no Chrome, mas o Internet Explorer parece ter um problema ao passar os dados fatiados para o Blob-Constructor. Na minha máquina, a transmissão de 5 MB de dados causa uma falha no Internet Explorer e o consumo de memória está aumentando. O Chrome cria o blob rapidamente.

Execute este código para uma comparação:

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

Então, decidi incluir os dois métodos descritos por Jeremy em uma função. Créditos vão para ele por isso.

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
fonte
Obrigado por incluir isso. Com uma atualização recente do IE11 (entre 5/2016 e 8/2016), a geração de blobs a partir de matrizes começou a tomar uma quantidade maior de RAM. Ao enviar um único Uint8Array para o construtor do blog, ele quase não utilizou ram e realmente concluiu o processo.
Andrew Vogel
Aumentar o tamanho da fatia na amostra de teste de 1K para 8..16K diminui significativamente o tempo no IE. No meu PC código original levou de 5 a 8 segundos, código com 8K blocos levou apenas 356ms e 225ms para blocos de 16K
Victor
5

Se você pode adicionar uma dependência ao seu projeto, existe o ótimo blob-utilpacote npm que fornece uma base64StringToBlobfunção útil . Uma vez adicionado ao seu, package.jsonvocê pode usá-lo assim:

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
fonte
5

Para todos os amantes de copiar e colar como eu, aqui está uma função de download preparada que funciona no 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);
    });
}
Eyup Yusein
fonte
o createObjectURLnão aceita um segundo argumento ...
Endless
3

Estou postando uma maneira mais declarativa de sincronizar a conversão do Base64. Embora o assíncrono fetch().blob()seja muito elegante e eu goste muito desta solução, ele não funciona no Internet Explorer 11 (e provavelmente o Edge - eu não testei este), mesmo com o polyfill - dê uma olhada no meu comentário para Endless ' postar para mais detalhes.

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

Bônus

Se você quiser imprimi-lo, pode fazer 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;
   }
};

Bônus x 2 - Abrir um arquivo BLOB em uma nova guia do Internet Explorer 11

Se você for capaz de executar algum pré-processamento da string Base64 no servidor, poderá expô-la em algum URL e usar o link em printJS:)

Papi
fonte
2

A seguir está o meu código TypeScript, que pode ser facilmente convertido em JavaScript e você pode 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
fonte
4
Embora esse snippet de código possa ser a solução, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
31519 Johan Johan
2
Além disso, por que você está gritando com comentários?
canbax
4
Seu Typescript codecódigo tem apenas um tipo ÚNICO e esse tipo é any. Como por que se preocupar?
zoran404 12/02
0

O método com busca é a melhor solução, mas se alguém precisar usar um método sem buscar, aqui está, pois os mencionados anteriormente não funcionaram para mim:

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
fonte