在JavaScript中从Base64字符串创建一个BLOB

我有一个base64编码的二进制数据字符串:

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

我想创建一个包含此数据的blob: URL,并将其显示给用户:

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


window.location = blobUrl;

我一直没能弄清楚如何创建BLOB。

在某些情况下,我可以通过使用data: URL来避免这种情况:

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


window.location = dataUrl;

然而,在大多数情况下,data: url非常大。


我如何解码一个Base64字符串到一个BLOB对象在JavaScript?

810341 次浏览

atob函数将一个base64编码的字符串解码为一个新的字符串,每个字节对应一个字符的二进制数据。

const byteCharacters = atob(b64Data);

每个字符的编码点(charCode)将是字节的值。我们可以为字符串中的每个字符使用.charCodeAt方法来创建一个字节值数组。

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

通过将字节值数组传递给Uint8Array构造函数,可以将其转换为真正类型的字节数组。

const byteArray = new Uint8Array(byteNumbers);

通过将其包装在数组中并将其传递给Blob构造函数,可以将其转换为BLOB。

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

上面的代码可以工作。但是,通过将byteCharacters分成更小的片处理,而不是一次性处理,可以稍微提高性能。在我的粗略测试中,512字节似乎是一个很好的切片大小。这就得到了下面的函数。

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;

完整的例子:

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

优化(但可读性较差)实现:

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

对于图像数据,我发现使用canvas.toBlob(异步)更简单。

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

对于所有浏览器的支持,特别是在Android上,也许你可以添加这个:

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

我注意到,当像Jeremy建议的那样对数据进行切片时,Internet Explorer 11变得异常缓慢。这对于Chrome来说是正确的,但是Internet Explorer在将切片数据传递给Blob-Constructor时似乎有一个问题。在我的机器上,传输5 MB的数据会导致internet Explorer崩溃,内存消耗也会飙升。Chrome创建blob在任何时间。

运行下面的代码进行比较:

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

所以我决定在一个函数中包含Jeremy描述的两种方法。这都归功于他。

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;
}
}
这是一个更小的方法,没有任何依赖项或库 它需要新的获取API。(# EYZ0) < / p >

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


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

使用此方法,您还可以轻松地获得ReadableStream, ArrayBuffer, text和JSON (供参考,这也适用于节点中的node-fetch)

作为函数:

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

但我鼓励你首先不要使用base64。有更好的方法来发送和接收二进制数据。JSON并不总是最好的选择。它占用更多的带宽和浪费处理时间(解码)的东西。用canvas.toBlob代替canvas.toDataURL,用FormData发送二进制文件。您还可以返回一个多部分有效负载,并使用来自服务器响应的await response.formData()对其进行解码。FormData可以是双向的。


我对Jeremy的ES6同步版本做了一个简单的性能测试 同步版本会阻塞UI一段时间。 打开devtool会降低读取性能

document.body.innerHTML += '<input autofocus placeholder="try writing">'
// get some dummy gradient image
var img=function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=b.createLinearGradient(0,0,1500,1500);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 = 500
console.time('blocking b64')
while (i--) {
await b64toBlob(b64Data)
}
console.timeEnd('blocking b64')
  

// bench non blocking
i = 500


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

请看这个例子: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);

如果你能忍受在你的项目中添加一个依赖项,那么blob-util npm包提供了一个方便的base64StringToBlob函数。一旦添加到你的package.json,你可以这样使用它:

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

我将发布一种更声明性的同步Base64转换方式。虽然async# EYZ0非常整洁,我非常喜欢这个解决方案,但它在Internet Explorer 11(可能是Edge -我还没有测试过这个)上不起作用,即使是在polyfill上-看看我对没完没了的文章的评论,了解更多细节。

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

奖金

如果你想打印它,你可以这样做:

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

红利x 2 -打开一个BLOB文件在新选项卡的Internet Explorer 11

如果你能够在服务器上对Base64字符串做一些预处理,你可以在一些URL下公开它,并使用printJS中的链接:)

带fetch的方法是最好的解决方案,但如果有人需要使用一个没有fetch的方法,那么这里就是,因为前面提到的那些对我来说不适用:

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

以下是我的TypeScript代码,可以很容易地转换成JavaScript,你可以使用

/**
* Convert BASE64 to BLOB
* @param base64Image Pass Base64 image data to convert into the BLOB
*/
private convertBase64ToBlob(base64Image: string) {
// 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 });
}

对于所有像我一样喜欢复制粘贴的人来说,这里有一个可以在Chrome、Firefox和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);
});
}

这将被证明是一个很短的解决办法。

const byteArray = new Buffer(base64String.replace(/^[\w\d;:\/]+base64\,/g, ''), 'base64');

base64String是包含以64为基数的字符串。

byteArray是你需要的数组。

regex替换是可选的,只是用于处理dataurl字符串中的前缀。

两种不同的变化

function base64ToBlob(base64, contentType='image/png', chunkLength=512) {
const byteCharsArray = Array.from(atob(base64.substr(base64.indexOf(',') + 1)));
const chunksIterator = new Array(Math.ceil(byteCharsArray.length / chunkLength));
const bytesArrays = [];


for (let c = 0; c < chunksIterator.length; c++) {
bytesArrays.push(new Uint8Array(byteCharsArray.slice(c * chunkLength, chunkLength * (c + 1)).map(s => s.charCodeAt(0))));
}


const blob = new Blob(bytesArrays, {type: contentType});
    

return blob;
}


/* Not sure how it performs with big images */
async function base64ToBlobLight(base64) { return await fetch(base64).then(res => res.blob()); }


/* Test */
const base64Data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAB+FBMVEUAAAA/mUPidDHiLi5Cn0XkNTPmeUrkdUg/m0Q0pEfcpSbwaVdKskg+lUP4zA/iLi3msSHkOjVAmETdJSjtYFE/lkPnRj3sWUs8kkLeqCVIq0fxvhXqUkbVmSjwa1n1yBLepyX1xxP0xRXqUkboST9KukpHpUbuvRrzrhF/ljbwaljuZFM4jELaoSdLtElJrUj1xxP6zwzfqSU4i0HYnydMtUlIqUfywxb60AxZqEXaoifgMCXptR9MtklHpEY2iUHWnSjvvRr70QujkC+pUC/90glMuEnlOjVMt0j70QriLS1LtEnnRj3qUUXfIidOjsxAhcZFo0bjNDH0xxNLr0dIrUdmntVTkMoyfL8jcLBRuErhJyrgKyb4zA/5zg3tYFBBmUTmQTnhMinruBzvvhnxwxZ/st+Ktt5zp9hqota2vtK6y9FemNBblc9HiMiTtMbFtsM6gcPV2r6dwroseLrMrbQrdLGdyKoobKbo3Zh+ynrgVllZulTsXE3rV0pIqUf42UVUo0JyjEHoS0HmsiHRGR/lmRz/1hjqnxjvpRWfwtOhusaz0LRGf7FEfbDVmqHXlJeW0pbXq5bec3fX0nTnzmuJuWvhoFFhm0FtrziBsjaAaDCYWC+uSi6jQS3FsSfLJiTirCOkuCG1KiG+wSC+GBvgyhTszQ64Z77KAAAARXRSTlMAIQRDLyUgCwsE6ebm5ubg2dLR0byXl4FDQzU1NDEuLSUgC+vr6urq6ubb29vb2tra2tG8vLu7u7uXl5eXgYGBgYGBLiUALabIAAABsElEQVQoz12S9VPjQBxHt8VaOA6HE+AOzv1wd7pJk5I2adpCC7RUcHd3d3fXf5PvLkxheD++z+yb7GSRlwD/+Hj/APQCZWxM5M+goF+RMbHK594v+tPoiN1uHxkt+xzt9+R9wnRTZZQpXQ0T5uP1IQxToyOAZiQu5HEpjeA4SWIoksRxNiGC1tRZJ4LNxgHgnU5nJZBDvuDdl8lzQRBsQ+s9PZt7s7Pz8wsL39/DkIfZ4xlB2Gqsq62ta9oxVlVrNZpihFRpGO9fzQw1ms0NDWZz07iGkJmIFH8xxkc3a/WWlubmFkv9AB2SEpDvKxbjidN2faseaNV3zoHXvv7wMODJdkOHAegweAfFPx4G67KluxzottCU9n8CUqXzcIQdXOytAHqXxomvykhEKN9EFutG22p//0rbNvHVxiJywa8yS2KDfV1dfbu31H8jF1RHiTKtWYeHxUvq3bn0pyjCRaiRU6aDO+gb3aEfEeVNsDgm8zzLy9egPa7Qt8TSJdwhjplk06HH43ZNJ3s91KKCHQ5x4sw1fRGYDZ0n1L4FKb9/BP5JLYxToheoFCVxz57PPS8UhhEpLBVeAAAAAElFTkSuQmCC';
        

const blob = base64ToBlob(base64Data);
const blobUrl = URL.createObjectURL(blob);
const img = document.createElement('img');


img.src = blobUrl;
document.body.appendChild(img);


/**********************/


/* Test */
(async () => {
const blob = await base64ToBlobLight(base64Data);
const blobUrl = URL.createObjectURL(blob);
const img = document.createElement('img');


img.src = blobUrl;
document.body.appendChild(img);
})();

在浏览器中

Uint8Array.from(atob(YOUR_BASE64_DATA), (c) => c.charCodeAt(0))

比较fetch

!(async () => {
const start = performance.now();
let i = 0;
while (i++ < 1e3) {
const dataUrl =
"data:application/octet-stream;base64,H4sIAAAAAAAAA0vOzyvOz0nVy8lP10jISM3JyVdIr8osUFCpdkksSdXLyy/X0KxN0ORKHlU3qm5U3ai6UXWj6kauOgBVt1KRLwcAAA==";
body = await (await fetch(dataUrl)).blob();
}
console.log(performance.now() - start); // 508.19999999925494ms
})();
!(async () => {
const start = performance.now();
let i = 0;
while (i++ < 1e3) {
const base64Data =
"H4sIAAAAAAAAA0vOzyvOz0nVy8lP10jISM3JyVdIr8osUFCpdkksSdXLyy/X0KxN0ORKHlU3qm5U3ai6UXWj6kauOgBVt1KRLwcAAA==";
body = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
}
console.log(performance.now() - start); // 7.899999998509884ms
})();

根据您的数据大小,选择性能一种。