Création d'un BLOB à partir d'une chaîne Base64 en JavaScript

447

J'ai des données binaires encodées en Base64 dans une chaîne:

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

Je souhaite créer une blob:URL contenant ces données et l'afficher à l'utilisateur:

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

window.location = blobUrl;

Je n'ai pas pu comprendre comment créer le BLOB.

Dans certains cas, je peux éviter cela en utilisant une data:URL à la place:

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

window.location = dataUrl;

Cependant, dans la plupart des cas, les data:URL sont trop grandes.


Comment décoder une chaîne Base64 en objet BLOB en JavaScript?

Jeremy Banks
la source

Réponses:

790

La atobfonction décode une chaîne encodée en Base64 en une nouvelle chaîne avec un caractère pour chaque octet des données binaires.

const byteCharacters = atob(b64Data);

Le point de code de chaque caractère (charCode) sera la valeur de l'octet. Nous pouvons créer un tableau de valeurs d'octets en appliquant ceci en utilisant la .charCodeAtméthode pour chaque caractère de la chaîne.

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

Vous pouvez convertir ce tableau de valeurs d'octets en un tableau d'octets typé réel en le passant au Uint8Arrayconstructeur.

const byteArray = new Uint8Array(byteNumbers);

Celui-ci peut à son tour être converti en BLOB en l'enveloppant dans un tableau et en le transmettant au Blobconstructeur.

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

Le code ci-dessus fonctionne. Cependant, les performances peuvent être améliorées un peu en traitant les byteCharactersplus petites tranches, plutôt que toutes à la fois. Dans mes tests approximatifs, 512 octets semblent être une bonne taille de tranche. Cela nous donne la fonction suivante.

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;

Exemple complet:

Jeremy Banks
la source
6
Salut Jeremy. Nous avons eu ce code dans notre application Web et il n'a posé aucun problème tant que les fichiers en cours de téléchargement n'étaient pas plus volumineux. Cela a donc provoqué des blocages et des plantages sur le serveur de production, lorsque les utilisateurs utilisaient Chrome ou IE pour télécharger des fichiers de plus de 100 Mo. Nous avons constaté que la ligne suivante dans IE augmentait l'exception de mémoire "var byteNumbers = new Array (slice.length)". Cependant en chrome, c'était la boucle for qui causait le même problème. Nous n'avons pas pu trouver une résolution appropriée à ce problème, puis nous sommes passés au téléchargement direct des fichiers à l'aide de window.open. Pouvez-vous fournir de l'aide ici?
Akshay Raut
Est-ce une méthode pour convertir un fichier vidéo en base64 dans React Native? J'ai réussi à le faire avec un fichier image mais je n'ai pas trouvé de solution pour les mêmes vidéos. Les liens seront utiles ou une solution également.
Diksha235
Il n'y a donc aucun problème à stocker des 0 dans la chaîne renvoyée par atob ()?
wcochran
cela n'a pas fonctionné pour moi pour certains blobs sur Chrome et Firefox mais a fonctionné sur le bord: /
Gragas Incoming
a fonctionné pour moi. son jette une erreur ** JSON Parse: toke non reconnu '<' ** j'ai vérifié la chaîne base64 en mettant dans un navigateur il fait une image. besoin d'aide.
Aman Deep
273

Voici une méthode plus minimale sans dépendances ni bibliothèques.
Il nécessite la nouvelle API d'extraction. ( Puis-je l'utiliser? )

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

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

Avec cette méthode, vous pouvez également obtenir facilement un ReadableStream, ArrayBuffer, du texte et du JSON.

En tant que fonction:

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

J'ai fait un test de performance simple vers la version de synchronisation ES6 de Jeremy.
La version de synchronisation bloquera l'interface utilisateur pendant un certain temps. garder l'outil devtool ouvert peut ralentir les performances de récupération

Interminable
la source
1
Est-ce que cela fonctionnera toujours si la taille de la chaîne encodée en base64 est grande, disons supérieure à 665536 caractères, ce qui est la limite pour les tailles d'URI dans Opera?
Daniel Kats
1
Je ne sais pas, je sais que cela peut être une limite à la barre d'adresse mais faire des choses avec AJAX pourrait être une exception car il n'a pas besoin d'être rendu. Vous devez le tester. Si c'était le cas, je n'aurais jamais obtenu la chaîne base64 en premier lieu. Penser que c'est une mauvaise pratique, prend plus de mémoire et de temps pour décoder et encoder. createObjectURLau lieu de, readAsDataURLc'est beaucoup mieux par exemple. Et si vous téléchargez des fichiers en utilisant ajax, choisissez à la FormDataplace de JSON, ou utilisez à la canvas.toBlobplace detoDataURL
Endless
7
Encore mieux en ligne:await (await fetch(imageDataURL)).blob()
icl7126
3
bien sûr, si vous ciblez le dernier navigateur. Mais cela nécessite que la fonction soit également à l'intérieur d'une fonction asynchrone. En parlant de ... await fetch(url).then(r=>r.blob())est trieur
Endless
2
Solution très soignée, mais selon mes connaissances ne fonctionnera pas avec IE (avec polyfill ofc) en raison d'une Access is denied.erreur. Je suppose que le fetchblob est exposé sous l'URL du blob - de la même manière URL.createObjectUrl- qui ne fonctionnera pas sur ie11. référence . Peut-être existe-t-il une solution de contournement pour utiliser la récupération avec IE11? Il semble beaucoup mieux que les autres solutions de synchronisation :)
Papi
72

Implémentation optimisée (mais moins lisible):

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
la source
2
Y a-t-il une raison de découper les octets en objets blob? Si je n'utilise pas, y a-t-il un inconvénient ou un risque?
Alfred Huang
Fonctionne très bien sur Android avec Ionic 1 / Angular 1. Une tranche est requise sinon je tombe sur OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann
4
Seul exemple, j'ai pu travailler de manière transparente avec n'importe quel type de document dans un environnement d'entreprise à la fois dans IE 11 et Chrome.
santos
C'est fantastique. Je vous remercie!
elliotwesoff
Une explication serait de mise. Par exemple, pourquoi a-t-il des performances plus élevées?
Peter Mortensen
19

Pour toute prise en charge du navigateur, en particulier sur Android, vous pouvez peut-être ajouter ceci:

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
la source
Merci, mais il y a DEUX problèmes sur l'extrait de code que vous avez écrit ci-dessus si je le lis correctement: (1) Le code dans catch () sur le dernier else-if est le même que le code original dans try (): "blob = new Blob (byteArrays, {type: contentType}) "... Je ne sais pas pourquoi vous proposez de répéter le même code après l'exception d'origine? ... (2) BlobBuilder.append () ne peut PAS accepter les tableaux d'octets mais ArrayBuffer. Ainsi, les tableaux d'octets en entrée doivent être davantage convertis en son ArrayBuffer avant d'utiliser cette API. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher
14

Pour les données d'image, je le trouve plus simple à utiliser canvas.toBlob(asynchrone)

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
la source
1
Je suppose que vous perdez des informations avec ça ... comme les méta-informations, c'est comme convertir n'importe quelle image en png, donc ce n'est pas le même résultat, cela ne fonctionne que pour les images
Endless
Je suppose que vous pouvez l'améliorer en extrayant le type d'image image/jpgde la chaîne base64, puis en le passant en tant que deuxième paramètre en toBlobfonction afin que le résultat soit du même type. En dehors de cela, je pense que c'est parfait - il économise 30% du trafic et de votre espace disque sur le serveur (par rapport à base64) et cela fonctionne bien même avec un PNG transparent.
icl7126
1
La fonction plante avec des images de plus de 2 Mo ... dans Android, je reçois l'exception: android.os.TransactionTooLarge
Ruben
14

Voir cet exemple: 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
la source
Une explication serait de mise.
Peter Mortensen
9

J'ai remarqué qu'Internet Explorer 11 devient incroyablement lent lors du découpage des données comme l'a suggéré Jeremy. Cela est vrai pour Chrome, mais Internet Explorer semble avoir un problème lors du transfert des données découpées au Blob-Constructor. Sur ma machine, le passage de 5 Mo de données fait planter Internet Explorer et la consommation de mémoire monte en flèche. Chrome crée le blob en un rien de temps.

Exécutez ce code pour une comparaison:

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

J'ai donc décidé d'inclure les deux méthodes décrites par Jeremy dans une seule fonction. Les crédits vont à lui pour cela.

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
la source
Merci d'avoir inclus cela. Avec une récente mise à jour d'IE11 (entre 5/2016 et 8/2016), la génération de blobs à partir de tableaux a commencé à prendre une quantité de RAM plus importante. En envoyant un seul Uint8Array dans le constructeur de blog, il n'a utilisé pratiquement aucun bélier et a effectivement terminé le processus.
Andrew Vogel
L'augmentation de la taille de tranche dans l'échantillon de test de 1K à 8..16K diminue considérablement le temps dans IE. Sur mon PC, le code d'origine a pris de 5 à 8 secondes, le code avec des blocs de 8K n'a pris que 356 ms et 225 ms pour des blocs de 16 Ko
Victor
6

Pour tous les amateurs de copier-coller comme moi, voici une fonction de téléchargement préparée qui fonctionne sur Chrome, Firefox et 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
la source
le createObjectURLne pas accepter un argument de 2 ...
sans fin
5

Si vous pouvez supporter d'ajouter une dépendance à votre projet, il y a le grand blob-utilpaquet npm qui fournit une base64StringToBlobfonction pratique . Une fois ajouté à votre, package.jsonvous pouvez l'utiliser comme ceci:

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
la source
3

Je publie un moyen plus déclaratif de synchronisation de la conversion Base64. Bien que l'async fetch().blob()soit très soigné et que j'aime beaucoup cette solution, elle ne fonctionne pas sur Internet Explorer 11 (et probablement Edge - je n'ai pas testé celui-ci), même avec le polyfill - jetez un œil à mon commentaire à Endless ' postez pour plus de détails.

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

Prime

Si vous voulez l'imprimer, vous pouvez faire quelque chose comme:

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 - Ouverture d'un fichier BLOB dans un nouvel onglet pour Internet Explorer 11

Si vous pouvez effectuer un prétraitement de la chaîne Base64 sur le serveur, vous pouvez l'exposer sous une URL et utiliser le lien dans printJS:)

Papi
la source
2

Voici mon code TypeScript qui peut être facilement converti en JavaScript et vous pouvez utiliser

/**
 * 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
la source
4
Bien que cet extrait de code puisse être la solution, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir et que ces personnes ne connaissent peut-être pas les raisons de votre suggestion de code.
Johan
2
Aussi, pourquoi criez-vous aux commentaires?
canbax
4
Votre Typescript codecode n'a qu'un seul type et ce type est any. Comme pourquoi même déranger ??
zoran404
0

La méthode avec fetch est la meilleure solution, mais si quelqu'un a besoin d'utiliser une méthode sans fetch alors la voici, car celles mentionnées précédemment ne fonctionnaient pas pour moi:

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
la source