在JavaScript中从Base64字符串创建BLOB


447

我在一个字符串中有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对象?

Answers:


788

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;

完整示例:


6
嗨,杰里米。我们的Web应用程序中已包含此代码,直到下载的文件更大后才引起任何问题。因此,当用户使用Chrome或IE下载大于100mb的文件时,它会导致生产服务器挂起并崩溃。我们发现IE中的下一行正在引发内存异常“ var byteNumbers = new Array(slice.length)”。但是在chrome中,它是for循环,导致同样的问题。我们找不到适合该问题的解决方案,然后我们转向使用window.open直接下载文件。您可以在这里提供一些帮助吗?
Akshay Raut

有什么方法可以在react native中将视频文件转换为base64吗?我设法用一个图像文件来做到这一点,但是没有找到解决该问题的解决方案。链接将对您有所帮助或提供解决方案。
Diksha235

因此,在atob()返回的字符串中存储0不会有问题吗?
wcochran

对于Chrome和Firefox上的某些问题,这对我来说不起作用,但在边缘上起作用:/
Gragas

为我工作。它会引发** JSON Parse错误:无法识别的标记'<'**我通过在浏览器中创建图像来检查base64字符串。需要一些帮助。
阿曼(Aman Deep)

272

这是一个没有任何依赖项或库的更简单的方法。
它需要新的提取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打开会降低获取性能


1
如果base64编码的字符串的大小很大(例如,大于665536个字符),这是Opera中URI大小的限制,这是否仍然有效?
丹尼尔·凯茨

1
不知道,我知道这可能是对地址栏的限制,但是使用AJAX进行操作可能是一个例外,因为它不必呈现。您必须进行测试。如果是我,我将永远不会获得base64字符串。认为这是一种不良做法,因此会占用更多的内存和时间来进行解码和编码。createObjectURL而不是readAsDataURL更好。如果您上传使用AJAX的文件,选择FormData来代替JSON,或者使用canvas.toBlob替代的toDataURL
无尽

7
作为内联甚至更好:await (await fetch(imageDataURL)).blob()
icl7126 '18

3
当然,如果您定位到最新的浏览器。但这也要求该函数位于异步函数内。谈到... await fetch(url).then(r=>r.blob())是分拣机
无尽的

2
非常整洁的解决方案,但据我所知,由于Access is denied.错误,它不适用于IE(带有polyfill ofc)。我想fetch以相同的方式在blob url下公开URL.createObjectUrlblob-在ie11上不起作用。参考。也许有一些解决方法可以将IE11与访存一起使用?它看起来比其他同步解决方案好得多:)
Papi

72

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

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

2
有什么理由将字节切成blob吗?如果我不使用,是否有任何不利条件或风险?
Alfred Huang

在带有Ionic 1 / Angular 1的Android上运行良好。需要切片,否则我会遇到OOM(Android 6.0.1)。
尔根'Kashban'Wahlmann '17

4
仅是示例,我可以在IE 11和Chrome的企业环境中无缝处理任何文档类型。
桑托斯

这是太棒了。谢谢!
elliotwesoff

一个解释将是有条理的。例如,为什么它具有更高的性能?
Peter Mortensen

19

对于所有浏览器支持,尤其是在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
Panini Luncher

14

对于图像数据,我发现它更易于使用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
    });

1
我想您会丢失一些信息……就像元信息一样,它会将任何图像转换为png,因此结果也不一样,这也仅适用于图像
Endless

我猜您可以通过image/jpg从base64字符串中提取图像类型,然后将其作为第二个参数传递给toBlob函数,从而使结果为相同类型,来改善它。除此之外,我认为这是完美的-它可以节省30%的流量和服务器上的磁盘空间(与base64相比),即使使用透明的PNG,效果也不错。
icl7126

1
该函数崩溃,并且图像大于2MB ...在Android中,我得到了例外:android.os.TransactionTooLarge
Ruben

14

参见以下示例: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);


一个解释将是有条理的。
Peter Mortensen

9

我注意到,像Jeremy建议的那样对数据进行切片时,Internet Explorer 11的速度变得异常慢。对于Chrome来说确实如此,但是将切片的数据传递给Blob构造函数时,Internet Explorer似乎有问题。在我的机器上,传递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;
    }
}

谢谢你包括这个。随着IE11的最新更新(在5/2016与8/2016之间),从数组生成斑点开始占用更大数量的RAM。通过将单个Uint8Array发送到博客构造函数中,它几乎不使用ram并实际上完成了该过程。
安德鲁·沃格尔

将测试样品的切片大小从1K增加到8..16K可以显着减少IE中的时间。在我的PC上,原始代码花了5到8秒,使用8K块的代码仅花费了356ms,而使用16K块的代码则花费了225ms
Victor

5

如果您愿意为项目添加一个依赖项,那么不错的blob-utilnpm软件包提供了一个方便的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...

5

对于像我这样的所有复制粘贴爱好者,这里有一个可在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);
    });
}

createObjectURL不接受第二个参数...
无尽的

3

我发布了一种更具声明性的同步Base64转换方式。尽管异步fetch().blob()非常简洁,我非常喜欢这种解决方案,但是即使使用了polyfill,它也无法在Internet Explorer 11上运行(可能是Edge-我还没有测试过该解决方案)-看看我对Endless的评论。发布更多详细信息。

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-在Internet Explorer 11的新选项卡中打开BLOB文件

如果您能够对服务器上的Base64字符串进行一些预处理,则可以将其公开到某些URL下并使用printJS:)中的链接


2

以下是我的TypeScript代码,可以轻松将其转换为JavaScript,您可以使用

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

4
尽管此代码段可能是解决方案,但包括说明确实有助于提高帖子的质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
约翰

2
另外,您为什么对评论大吼?
canbax

4
您的Typescript code代码只有SINGLE类型,而该类型是any。就像为什么还要打扰?
zoran404

0

带有访存的方法是最好的解决方案,但是如果有人需要使用不访存的方法,那么这里就是,因为前面提到的方法对我不起作用:

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 });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.