在 JavaScript 中从 Base64 字符串创建 BLOB

IT技术 javascript base64
2021-01-31 19:40:21

我在字符串中有 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 大得令人望而却步。


如何在 JavaScript 中将 Base64 字符串解码为 BLOB 对象?

6个回答

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;

完整示例:

救了我。我快疯了,试图实现 excel 文件下载。
2021-03-12 19:40:21
嗨,杰里米。我们在我们的 Web 应用程序中有此代码,并且在下载的文件更大之前它不会引起任何问题。因此,当用户使用 Chrome 或 IE 下载大于 100mb 的文件时,它会导致生产服务器挂起和崩溃。我们发现 IE 中的以下行引发内存异常“var byteNumbers = new Array(slice.length)”。但是在 chrome 中,是 for 循环导致了同样的问题。我们找不到此问题的适当解决方案,然后我们转而使用 window.open 直接下载文件。你能在这里提供一些帮助吗?
2021-03-28 19:40:21
很好的答案。非常感谢
2021-04-05 19:40:21
这对我在 Chrome 和 Firefox 上的一些 blob 不起作用,但在边缘工作:/
2021-04-07 19:40:21

这是一个更简单的方法,没有任何依赖项或库。
它需要新的 fetch API。可以用吗?

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

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

使用此方法,您还可以轻松获取 ReadableStream、ArrayBuffer、文本和 JSON。
(仅供参考,这也适用于节点中的节点获取

作为一个函数:

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

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

如果 base64 编码字符串的大小很大,假设大于 665536 个字符,这仍然有效,这是 Opera 中 URI 大小的限制吗?
2021-03-17 19:40:21
甚至更好的内联: await (await fetch(imageDataURL)).blob()
2021-03-19 19:40:21
不知道,我知道它可能是对地址栏的一个限制,但使用 AJAX 做事可能是一个例外,因为它不必被渲染。你必须测试它。如果它在我那里,我一开始就不会得到 base64 字符串。认为这是一种不好的做法,会占用更多的内存和时间来解码和编码。例如,createObjectURL而不是readAsDataURL要好得多。如果您使用 ajax 上传文件,请选择FormData代替JSON,或使用canvas.toBlob代替toDataURL
2021-03-20 19:40:21
当然,如果您的目标是最新的浏览器。但这也要求该函数也位于异步函数内。说起来……await fetch(url).then(r=>r.blob())是分拣机
2021-03-25 19:40:21
非常简洁的解决方案,但据我所知,由于Access is denied.错误,它不适用于 IE(使用 polyfill ofc)我想fetch在 blob url 下暴露 blob - 以同样的方式URL.createObjectUrl- 这在 ie11 上不起作用。参考也许有一些解决方法可以在 IE11 中使用 fetch?它看起来比其他同步解决方案好得多:)
2021-04-05 19:40:21

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

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 });
}
是否有任何理由将字节切成 blob?如果我不使用,是否有任何不利或风险?
2021-03-23 19:40:21
使用 Ionic 1 / Angular 1 在 Android 上运行良好。需要切片,否则我会遇到 OOM(Android 6.0.1)。
2021-04-01 19:40:21
一个解释将是有序的。例如,为什么它具有更高的性能?
2021-04-02 19:40:21
唯一的例子是我可以在 IE 11 和 Chrome 的企业环境中无缝处理任何文档类型。
2021-04-06 19:40:21
@PeterMortensen 几年前,现在您可以尝试执行几种变体。想法是在重组循环条件检查和局部变量中。
2021-04-06 19:40:21

对于所有浏览器支持,尤其是在 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
    }
}
谢谢,但如果我没看错,你上面写的代码片段有两个问题:(1) 最后一个 else-if 中 catch() 中的代码与 try() 中的原始代码相同:“blob = new Blob(byteArrays, {type : contentType})" ...我不知道你为什么建议在原始异常后重复相同的代码?... (2) BlobBuilder.append() 不能接受字节数组,但可以接受 ArrayBuffer。因此,在使用此 API 之前,必须将输入的字节数组进一步转换为其 ArrayBuffer。参考:developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
2021-03-17 19:40:21

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

一个解释将是有序的。
2021-04-01 19:40:21