J'ai des données binaires encodées en base64 dans une chaîne.
var contentType = 'image/png';
var b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
Je souhaite créer une URL blob:
contenant ces données et l'afficher à l'utilisateur.
var blob = new Blob(????, {type: contentType});
var blobUrl = URL.createObjectURL(blob);
window.location = blobUrl;
Je n'ai pas été capable de comprendre comment créer la Blob
.
Dans certains cas, je peux éviter cela en utilisant plutôt une URL data:
.
var dataUrl = 'data:' + contentType + ';base64,' + b64Data;
window.location = dataUrl;
Cependant, dans la plupart des cas, les URL data:
sont prohibitives.
Comment décoder une chaîne base64 en un objet Blob
en JavaScript?
La fonction décrite ci-dessous est disponible sur NPM :
var b64toBlob = require('b64-to-blob')
La fonction atob
décodera une chaîne codée en base64 en une nouvelle chaîne avec un caractère pour chaque octet des données binaires.
var 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 l'appliquant à l'aide de la méthode .charCodeAt
pour chaque caractère de la chaîne.
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
Vous pouvez convertir ce tableau de valeurs d'octet en un tableau d'octets typé réel en le transmettant au constructeur Uint8Array
.
var byteArray = new Uint8Array(byteNumbers);
Cela peut à son tour être converti en Blob
en l'enveloppant dans un tableau en le transmettant au constructeur Blob
.
var blob = new Blob([byteArray], {type: contentType});
Le code ci-dessus fonctionne. Toutefois, les performances peuvent être légèrement améliorées en traitant la byteCharacters
en tranches plus petites, plutôt qu’ensemble. Dans mes tests approximatifs, 512 octets semblent être une bonne taille de tranche. Cela nous donne la fonction suivante.
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 blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);
window.location = blobUrl;
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 = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);
var img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);
'use strict';
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 contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
const blob = b64toBlob(b64Data, contentType);
const blobUrl = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);
Impossible d’éviter de publier la méthode minimaliste sans dépendance 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(blob => console.log(blob))
Avec cette méthode, vous pouvez également facilement obtenir arraybuffer, text et json
J'ai fait un test de performance simple pour la version es6 sync de Jeremy.
La version de synchronisation bloquera l’UI pendant un moment.
// get some dummy gradient image
var img=function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=b.createLinearGradient(0,0,200,100);a.width=a.height=3000;c.addColorStop(0,"red");c.addColorStop(1,"blue");b.fillStyle=c;b.fillRect(0,0,a.width,a.height);return a.toDataURL()}();
async function perf() {
const blob = await fetch(img).then(res => res.blob())
// turn it to a dataURI
const url = img
const b64Data = url.split(',')[1]
// Jeremy Banks solution
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;
}
// bench blocking method
let i = 1000
console.time('b64')
while (i--) {
await b64toBlob(b64Data)
}
console.timeEnd('b64')
// bench non blocking
i = 1000
// so that the function is not reconstructed each time
const toBlob = res => res.blob()
console.time('fetch')
while (i--) {
await fetch(url).then(toBlob)
}
console.timeEnd('fetch')
console.log('done')
}
perf()
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 });
}
Pour tout support de navigateur, en particulier sur Android . Peut-être que vous pouvez ajouter ceci
try{
blob = new Blob( byteArrays, {type : contentType});
}
catch(e){
// TypeError old chrome and FF
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
}
}
Pour les données d'image, je trouve plus simple d'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
});
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);
J'ai remarqué qu'Internet Explorer 11 devenait incroyablement lent lors du découpage des données, comme le suggère Jeremy. Cela est vrai pour Chrome, mais IE semble rencontrer un problème lors du transfert des données découpées en tranches au constructeur de blocs. Sur ma machine, le fait de transmettre 5 Mo de données provoque le blocage de IE et la consommation de mémoire augmente considérablement. Chrome crée le blob en un rien de temps.
Exécutez ce code pour un comparisme:
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 fonction. Les crédits lui vont 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 IE 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 IE 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;
}
}
Si vous pouvez supporter l’ajout d’une dépendance à votre projet, le package génial blob-util
npm fournit une fonction pratique base64StringToBlob
. Une fois ajouté à votre package.json
, vous 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...
Voici mon code TypeScript qui peut être converti facilement en javascript et que 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 });
}
Je poste une manière plus déclarative de convertir la base64 en synchronisation. Quand async fetch().blob()
est très soigné et que cette solution me plait beaucoup, elle ne fonctionne pas sous IE11 (et probablement Edge - je n'ai pas testé celui-ci), même avec polyfill - jetez un coup d'œil à mon commentaire sur Endless post 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 le faire 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;
}
};
BONUSx2 - Ouverture du fichier blob dans le nouvel onglet pour IE11
Si vous pouvez effectuer un traitement préalable de la chaîne base64 sur le serveur, vous pouvez l'exposer sous une URL et utiliser le lien dans printJS
:)
La méthode avec fetch est la meilleure solution, mais si quelqu'un a besoin de méthode sans fetch, la voici: celles mentionnées ci-dessus ne m'ont pas fonctionné
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 });
}