Tạo BLOB từ chuỗi Base64 trong JavaScript


447

Tôi có dữ liệu nhị phân được mã hóa Base64 trong một chuỗi:

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

Tôi muốn tạo một blob:URL chứa dữ liệu này và hiển thị nó cho người dùng:

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

window.location = blobUrl;

Tôi chưa thể tìm ra cách tạo BLOB.

Trong một số trường hợp, tôi có thể tránh điều này bằng cách sử dụng data:URL thay thế:

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

window.location = dataUrl;

Tuy nhiên, trong hầu hết các trường hợp, các data:URL rất lớn.


Làm cách nào để giải mã chuỗi Base64 thành đối tượng BLOB trong JavaScript?

Câu trả lời:


789

Các atobchức năng sẽ giải mã một chuỗi Base64 mã hóa thành một chuỗi mới với một ký tự cho mỗi byte của dữ liệu nhị phân.

const byteCharacters = atob(b64Data);

Mỗi điểm mã của ký tự (charCode) sẽ là giá trị của byte. Chúng ta có thể tạo một mảng các giá trị byte bằng cách áp dụng .charCodeAtphương thức này bằng cách sử dụng phương thức cho từng ký tự trong chuỗi.

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

Bạn có thể chuyển đổi mảng giá trị byte này thành một mảng byte được gõ thực sự bằng cách chuyển nó đến hàm Uint8Arraytạo.

const byteArray = new Uint8Array(byteNumbers);

Đến lượt nó có thể được chuyển đổi thành BLOB bằng cách gói nó trong một mảng và chuyển nó đến hàm Blobtạo.

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

Các mã trên hoạt động. Tuy nhiên, hiệu suất có thể được cải thiện một chút bằng cách xử lý các byteCharacterslát nhỏ hơn thay vì tất cả cùng một lúc. Trong thử nghiệm thô của tôi, 512 byte dường như là một kích thước lát cắt tốt. Điều này cho chúng ta các chức năng sau đây.

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;

Ví dụ đầy đủ:


6
Xin chào Jeremy. Chúng tôi đã có mã này trong ứng dụng web của mình và nó không gây ra vấn đề gì cho đến khi các tệp được tải xuống có kích thước lớn hơn. Vì vậy, nó đã gây ra treo và treo trong máy chủ sản xuất, khi người dùng đang sử dụng Chrome hoặc IE để tải xuống các tệp lớn hơn 100mb. Chúng tôi thấy rằng dòng sau IE đã tăng ngoại lệ bộ nhớ "var byteNumbers = new Array (lát.length)". Tuy nhiên, trong chrome, đó là vòng lặp for gây ra vấn đề tương tự. Chúng tôi không thể tìm thấy giải pháp phù hợp cho vấn đề này sau đó chúng tôi chuyển sang tải trực tiếp các tệp bằng window.open. Bạn có thể cung cấp một số trợ giúp ở đây?
Akshay Raut

Có bất kỳ phương pháp nào để chuyển đổi một tập tin video thành base64 trong phản ứng gốc? Tôi đã quản lý để làm như vậy với một tệp hình ảnh nhưng không tìm thấy giải pháp tương tự cho video. Liên kết sẽ hữu ích hoặc một giải pháp cũng có.
Diksha235

Vì vậy, không có vấn đề lưu trữ 0 trong chuỗi được trả về bởi atob ()?
wcochran

điều này không hiệu quả với tôi đối với một số đốm màu trên Chrome và Firefox nhưng hoạt động ở rìa: /
Gragas Đến vào

đã làm việc cho tôi. nó ném một lỗi ** JSON Parse: Không nhận ra toke '<' ** tôi đã kiểm tra chuỗi base64 bằng cách đưa vào trình duyệt nó đang tạo một hình ảnh. cần giúp đỡ.
Aman Sâu

272

Đây là một phương pháp tối thiểu hơn mà không có bất kỳ phụ thuộc hoặc thư viện.
Nó đòi hỏi API tìm nạp mới. ( Tôi có thể sử dụng nó không? )

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

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

Với phương pháp này, bạn cũng có thể dễ dàng có được ReadableStream, ArrayBuffer, văn bản và JSON.

Là một chức năng:

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

Tôi đã thực hiện một bài kiểm tra hiệu suất đơn giản đối với phiên bản đồng bộ ES6 của Jeremy.
Phiên bản đồng bộ sẽ chặn UI trong một thời gian. giữ devtool mở có thể làm chậm hiệu suất tìm nạp


1
Điều này sẽ vẫn hoạt động nếu kích thước của chuỗi được mã hóa base64 lớn, giả sử lớn hơn 665536 ký tự, đó là giới hạn cho kích thước URI trong Opera?
Daniel Kats

1
Không biết, tôi biết nó có thể là một giới hạn cho thanh địa chỉ nhưng làm mọi thứ với AJAX có thể là một ngoại lệ vì nó không phải được hiển thị. Bạn phải kiểm tra nó. Nếu nó ở nơi tôi, tôi sẽ không bao giờ có được chuỗi base64 ở vị trí đầu tiên. Nghĩ rằng đó là một thực hành tồi, chiếm nhiều bộ nhớ và thời gian để giải mã và mã hóa. createObjectURLthay vì readAsDataURLtốt hơn nhiều chẳng hạn. Và nếu bạn tải lên các tệp bằng ajax, hãy chọn FormDatathay vì JSONhoặc sử dụng canvas.toBlobthay vìtoDataURL
Vô tận

7
Thậm chí tốt hơn như nội tuyến:await (await fetch(imageDataURL)).blob()
icl7126

3
chắc chắn, nếu bạn nhắm mục tiêu trình duyệt mới nhất. Nhưng điều đó cũng đòi hỏi chức năng phải nằm trong chức năng không đồng bộ. Nói về ... await fetch(url).then(r=>r.blob())là máy phân loại
Vô tận

2
Giải pháp rất gọn gàng, nhưng theo kiến ​​thức của tôi sẽ không hoạt động với IE (với polyfill ofc) do Access is denied.lỗi. Tôi đoán fetchphơi bày blob dưới url blob - theo cách tương tự URL.createObjectUrl- không hoạt động trên eg11. tài liệu tham khảo . Có lẽ có một số cách giải quyết để sử dụng tìm nạp với IE11? Nó có vẻ tốt hơn nhiều so với các giải pháp đồng bộ hóa khác :)
Papi

72

Thực hiện tối ưu hóa (nhưng ít đọc hơn):

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
Có bất kỳ lý do để cắt các byte thành đốm? Nếu tôi không sử dụng, có bất lợi hay rủi ro nào không?
Alfred Huang

Hoạt động tuyệt vời trên Android với Ionic 1 / Angular 1. Slice là bắt buộc nếu không tôi chạy vào OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann

4
Chỉ có ví dụ ngoài đó tôi có thể làm việc liền mạch với bất kỳ loại tài liệu nào trong môi trường doanh nghiệp trong cả IE 11 và Chrome.
santos

Cái này thật tuyệt. Cảm ơn bạn!
elliotwesoff

Một lời giải thích sẽ theo thứ tự. Ví dụ, tại sao nó có hiệu suất cao hơn?
Peter Mortensen

19

Đối với tất cả các hỗ trợ trình duyệt, đặc biệt là trên Android, có lẽ bạn có thể thêm điều này:

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

Cảm ơn, nhưng có HAI vấn đề về đoạn mã bạn đã viết ở trên nếu tôi đọc đúng: (1) Mã trong Catch () trên cái khác-if giống với mã gốc trong try (): "blob = Blob mới (byteArrays, {type: contentType}) "... Tôi không biết tại sao bạn đề nghị lặp lại cùng một mã sau ngoại lệ ban đầu? ... (2) BlobBuilder.append () KHÔNG thể chấp nhận mảng byte nhưng ArrayBuffer. Vì vậy, các mảng byte đầu vào phải được chuyển đổi thêm thành ArrayBuffer của nó trước khi sử dụng API này. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini trưa

14

Đối với dữ liệu hình ảnh, tôi thấy sử dụng đơn giản hơn canvas.toBlob(không đồng bộ)

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
Tôi đoán bạn mất một số thông tin với điều đó ... như thông tin meta giống như chuyển đổi bất kỳ hình ảnh nào sang png, vì vậy nó không phải là kết quả tương tự, điều này chỉ hoạt động đối với hình ảnh
Endless

Tôi đoán bạn có thể cải thiện nó bằng cách trích xuất loại hình ảnh image/jpgtừ chuỗi base64 và sau đó chuyển nó dưới dạng tham số thứ hai vào toBlobhàm để kết quả là cùng loại. Ngoài ra, tôi nghĩ rằng điều này là hoàn hảo - nó tiết kiệm 30% lưu lượng và dung lượng ổ đĩa của bạn trên máy chủ (so với base64) và nó hoạt động tốt ngay cả với PNG trong suốt.
icl7126

1
Chức năng gặp sự cố với hình ảnh lớn hơn 2MB ... trong Android Tôi có ngoại lệ: android.os.TransactionTooLarge
Ruben

14

Xem ví dụ này: 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);


Một lời giải thích sẽ theo thứ tự.
Peter Mortensen

9

Tôi nhận thấy rằng Internet Explorer 11 trở nên cực kỳ chậm khi cắt dữ liệu như Jeremy đề xuất. Điều này đúng với Chrome, nhưng Internet Explorer dường như có vấn đề khi chuyển dữ liệu được cắt ra cho Trình xây dựng Blob. Trên máy của tôi, việc truyền 5 MB dữ liệu khiến Internet Explorer gặp sự cố và tiêu thụ bộ nhớ đang đi qua mái nhà. Chrome tạo blob ngay lập tức.

Chạy mã này để so sánh:

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

Vì vậy, tôi quyết định đưa cả hai phương pháp được Jeremy mô tả vào một hàm. Tín dụng cho anh ta cho điều này.

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

Cảm ơn bạn đã bao gồm điều này. Với bản cập nhật gần đây cho IE11 (trong khoảng thời gian từ 5/2016 đến 8/2016), việc tạo các đốm màu từ các mảng bắt đầu lấy một lượng ram lớn hơn. Bằng cách gửi một Uint8Array vào trình xây dựng blog, nó gần như không sử dụng ram và thực sự đã hoàn thành quá trình.
Andrew Vogel

Tăng kích thước lát trong mẫu thử từ 1K lên 8..16K giảm đáng kể thời gian trong IE. Trên PC của tôi, mã gốc mất từ ​​5 đến 8 giây, mã với các khối 8K chỉ mất 356ms và 225ms cho các khối 16K
Victor

5

Nếu bạn có thể đứng thêm một phụ thuộc vào dự án của mình thì có blob-utilgói npm tuyệt vời cung cấp base64StringToBlobchức năng tiện dụng . Sau khi thêm vào, package.jsonbạn có thể sử dụng nó như thế này:

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

Đối với tất cả những người yêu thích sao chép giống như tôi, đây là chức năng tải xuống được nấu chín hoạt động trên Chrome, Firefox và 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);
    });
}

những createObjectURLkhông chấp nhận một cuộc tranh luận thứ 2 ...
Endless

3

Tôi đang đăng một cách khai báo đồng bộ hóa Base64. Mặc dù async fetch().blob()rất gọn gàng và tôi rất thích giải pháp này, nhưng nó không hoạt động trên Internet Explorer 11 (và có lẽ là Edge - tôi chưa thử nghiệm cái này), ngay cả với polyfill - hãy xem nhận xét của tôi về Endless ' đăng để biết thêm chi tiết.

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

Tặng kem

Nếu bạn muốn in nó, bạn có thể làm một cái gì đó như:

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

Phần thưởng x 2 - Mở tệp BLOB trong tab mới cho Internet Explorer 11

Nếu bạn có thể thực hiện một số tiền xử lý chuỗi Base64 trên máy chủ, bạn có thể hiển thị chuỗi đó dưới một số URL và sử dụng liên kết trong printJS:)


2

Sau đây là mã TypeScript của tôi có thể dễ dàng chuyển đổi thành JavaScript và bạn có thể sử dụng

/**
 * 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
Mặc dù đoạn mã này có thể là giải pháp, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
Johan

2
Ngoài ra, tại sao bạn lại VÀNG Ý kiến?
canbax

4
Typescript codeMã của bạn chỉ có một loại SINGLE và loại đó là any. Giống như tại sao thậm chí bận tâm ??
zoran404

0

Phương thức với tìm nạp là giải pháp tốt nhất, nhưng nếu bất cứ ai cần sử dụng một phương thức mà không tìm nạp thì đây là, vì các phương pháp được đề cập trước đây không hoạt động với tôi:

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 });
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.