Creating a Blob from a base64 string in JavaScript
我在一个字符串中有base64编码的二进制数据。
1 2 | const contentType = 'image/png'; const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; |
我想创建一个包含此数据的
1 2 3 4 | const blob = new Blob(????, {type: contentType}); const blobUrl = URL.createObjectURL(blob); window.location = blobUrl; |
我还没搞清楚如何创建
在某些情况下,我可以使用
1 2 3 | const dataUrl = `data:${contentType};base64,${b64Data}`; window.location = dataUrl; |
然而,在大多数情况下,
如何在javascript中将base64字符串解码为
1 | const byteCharacters = atob(b64Data); |
每个字符的码位(charcode)将是字节的值。我们可以通过对字符串中的每个字符使用
1 2 3 4 | const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } |
通过将这个字节值数组传递给
1 | const byteArray = new Uint8Array(byteNumbers); |
反过来,通过将它包装在数组中并传递给
1 | const blob = new Blob([byteArray], {type: contentType}); |
以上代码有效。但是,通过在较小的片中处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } |
1 2 3 4 | const blob = b64toBlob(b64Data, contentType); const blobUrl = URL.createObjectURL(blob); window.location = blobUrl; |
完整例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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); |
这里有一个更简单的方法,没有任何依赖项或库。它需要新的fetch API。(我能用吗?)
1 2 3 4 5 | var url ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" fetch(url) .then(res => res.blob()) .then(blob => console.log(blob)) |
使用此方法,您还可以轻松获取arraybuffer、text和json。
作为异步函数:
1 2 3 4 5 6 | const b64toBlob = async (b64Data, contentType='application/octet-stream') => { const url = `data:${contentType};base64,${b64Data}`; const response = await fetch(url); const blob = await response.blob(); return blob; }; |
我对Jeremy的ES6同步版本做了一个简单的性能测试。同步版本将暂时阻止用户界面。保持devtool打开会降低获取性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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 = 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() |
优化(但可读性较差)实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 }); } |
所有浏览器支持,特别是Android。也许你可以加上这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 } } |
对于图像数据,我发现使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 }); |
请参见以下示例:https://jsfiddle.net/pqhdce2l/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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); |
我注意到,InternetExplorer11在像Jeremy建议的那样对数据进行切片时速度非常慢。这对chrome是正确的,但ie在将切片数据传递给blob构造函数时似乎有问题。在我的机器上,传递5 MB的数据会导致IE崩溃,内存消耗也会增加。Chrome很快就创建了blob。
运行此代码进行比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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描述的两种方法都包含在一个函数中。这件事要归功于他。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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; } } |
如果你能在你的项目中增加一个依赖,那么有一个伟大的
1 2 3 4 5 6 7 8 | 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
1 2 3 4 5 6 7 8 | const blobPdfFromBase64String = base64String => { const byteArray = Uint8Array.from( atob(base64String) .split('') .map(char => char.charCodeAt(0)) ); return new Blob([byteArray], { type: 'application/pdf' }); }; |
奖金
如果你想打印它,你可以做如下的事情:
1 2 3 4 5 6 7 8 9 10 | 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-在IE11的新选项卡中打开blob文件
如果您能够在服务器上对base64字符串进行一些预处理,那么您可以在某个URL下公开它,并使用EDOCX1中的链接(11:)。
下面是我的typescript代码,可以很容易地转换成javascript,您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * 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 }); } |
带fetch的方法是最好的解决方案,但是如果有人需要不带fetch的方法,那么这里就是了,因为上面提到的方法对我不起作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 }); } |