Membuat BLOB dari string Base64 di JavaScript

447

Saya memiliki data biner yang disandikan Base64 dalam sebuah string:

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

Saya ingin membuat blob:URL yang berisi data ini dan menampilkannya kepada pengguna:

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

window.location = blobUrl;

Saya belum bisa menemukan cara membuat BLOB.

Dalam beberapa kasus, saya dapat menghindari ini dengan menggunakan data:URL sebagai gantinya:

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

window.location = dataUrl;

Namun, dalam kebanyakan kasus, data:URL-nya sangat besar.


Bagaimana saya bisa mendekode string Base64 ke objek BLOB di JavaScript?

Jeremy Banks
sumber

Jawaban:

789

The atobfungsi akan memecahkan kode string Base64 menjadi string baru dengan karakter untuk setiap byte dari data biner.

const byteCharacters = atob(b64Data);

Setiap titik kode karakter (charCode) akan menjadi nilai byte. Kita bisa membuat array nilai byte dengan menerapkan ini menggunakan .charCodeAtmetode untuk setiap karakter dalam string.

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

Anda dapat mengubah array nilai byte ini menjadi array byte yang diketik nyata dengan meneruskannya ke Uint8Arraykonstruktor.

const byteArray = new Uint8Array(byteNumbers);

Ini pada gilirannya dapat dikonversi ke BLOB dengan membungkusnya dalam sebuah array dan meneruskannya ke Blobkonstruktor.

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

Kode di atas berfungsi. Namun kinerjanya dapat ditingkatkan sedikit dengan memproses byteCharactersirisan yang lebih kecil, daripada sekaligus. Dalam pengujian kasar saya 512 byte tampaknya merupakan ukuran irisan yang baik. Ini memberi kita fungsi berikut.

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;

Contoh Lengkap:

Jeremy Banks
sumber
6
Hai Jeremy. Kami telah memiliki kode ini di aplikasi web kami dan itu tidak menyebabkan masalah sampai file yang diunduh berukuran lebih besar. Jadi itu menyebabkan hang dan crash di server produksi, ketika pengguna menggunakan Chrome atau IE untuk mengunduh file yang lebih besar dari 100mb. Kami menemukan bahwa di IE baris berikut meningkatkan pengecualian memori "var byteNumbers = new Array (slice.length)". Namun di chrome, itu adalah for loop yang menyebabkan masalah yang sama. Kami tidak dapat menemukan resolusi yang tepat untuk masalah ini kemudian kami pindah ke mengunduh file langsung menggunakan window.open. Bisakah Anda memberikan bantuan di sini?
Akshay Raut
Apakah itu metode untuk mengkonversi file video ke base64 di reaksi asli? Saya berhasil melakukannya dengan file gambar tetapi tidak menemukan solusi yang sama untuk video. Tautan juga akan bermanfaat atau solusi.
Diksha235
Jadi tidak ada masalah menyimpan 0 di string yang dikembalikan oleh atob ()?
wcochran
ini tidak berfungsi untuk saya untuk beberapa gumpalan di Chrome dan Firefox tetapi berhasil di tepi: /
Gragas Incoming
tidak bekerja untuk saya. itu melempar kesalahan JSON Parse **: Token tidak dikenal '<' ** saya memeriksa string base64 dengan memasukkan browser yang sedang membuat gambar. Butuh pertolongan.
Aman Deep
272

Berikut adalah metode yang lebih minimal tanpa dependensi atau pustaka.
Itu membutuhkan API ambil yang baru. ( Bisakah saya menggunakannya? )

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

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

Dengan metode ini Anda juga dapat dengan mudah mendapatkan ReadableStream, ArrayBuffer, teks, dan JSON.

Sebagai fungsi:

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

Saya melakukan tes kinerja sederhana terhadap versi sinkronisasi ES6 Jeremy.
Versi sinkronisasi akan memblokir UI untuk sementara waktu. menjaga devtool tetap terbuka dapat memperlambat kinerja pengambilan

Tak berujung
sumber
1
Apakah ini masih berfungsi jika ukuran string yang dikodekan base64 besar, katakanlah lebih besar dari 665536 karakter, yang merupakan batas untuk ukuran URI di Opera?
Daniel Kats
1
Tidak tahu, saya tahu itu bisa menjadi batasan untuk adressbar tetapi melakukan hal-hal dengan AJAX mungkin merupakan pengecualian karena tidak harus dirender. Anda harus mengujinya. Jika di tempat saya, saya tidak akan pernah mendapatkan string base64 di tempat pertama. Berpikir itu praktik yang buruk, membutuhkan lebih banyak memori dan waktu untuk memecahkan kode dan menyandikan. createObjectURLbukannya readAsDataURLjauh lebih baik misalnya. Dan jika Anda mengunggah file menggunakan ajax, pilih FormDatasebagai gantinya JSON, atau gunakan canvas.toBlobsebagai gantinyatoDataURL
Endless
7
Bahkan lebih baik seperti inline:await (await fetch(imageDataURL)).blob()
icl7126
3
tentu, jika Anda menargetkan browser terbaru. Tetapi itu membutuhkan fungsi untuk berada di dalam fungsi async juga. Berbicara tentang ... await fetch(url).then(r=>r.blob())penyortir
Tak Berujung
2
Solusi yang sangat rapi, tetapi menurut pengetahuan saya tidak akan bekerja dengan IE (dengan polyfill ofc) karena Access is denied.kesalahan. Saya kira fetchmengekspos gumpalan di bawah gumpalan url - dengan cara yang sama URL.createObjectUrltidak - yang tidak akan berfungsi pada ie11. referensi . Mungkin ada beberapa solusi untuk menggunakan fetch dengan IE11? Ini terlihat jauh lebih baik daripada solusi sinkronisasi lainnya :)
Papi
72

Implementasi yang dioptimalkan (tetapi kurang dapat dibaca):

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 });
}
Bacher
sumber
2
Apakah ada alasan untuk memotong byte menjadi gumpalan? Jika saya tidak menggunakan, apakah ada kerugian atau risiko?
Alfred Huang
Bekerja sangat baik di Android dengan Ionic 1 / Angular 1. Diperlukan Slice jika tidak, saya mengalami OOM (Android 6.0.1).
Jürgen 'Kashban' Wahlmann
4
Hanya contoh di luar sana saya bisa bekerja dengan mulus dengan semua jenis dokumen di lingkungan perusahaan di IE 11 dan Chrome.
santos
Ini fantastis. Terima kasih!
elliotwesoff
Penjelasan akan diurutkan. Misalnya, mengapa ia memiliki kinerja yang lebih tinggi?
Peter Mortensen
19

Untuk semua dukungan browser, terutama di Android, mungkin Anda dapat menambahkan ini:

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
    }
}
Jayce Lin
sumber
Terima kasih, tetapi ada DUA masalah pada cuplikan kode yang Anda tulis di atas jika saya membacanya dengan benar: (1) Kode di dalam tangkapan () pada yang lain-jika sama dengan kode asli dalam coba (): "blob = Blob baru (byteArrays, {type: contentType}) "... Saya tidak tahu mengapa Anda menyarankan untuk mengulang kode yang sama setelah pengecualian asli? ... (2) BlobBuilder.append () TIDAK dapat menerima byte-array tetapi ArrayBuffer. Jadi, input byte-array harus dikonversi lebih jauh ke dalam ArrayBuffer sebelum menggunakan API ini. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher
14

Untuk data gambar, saya merasa lebih mudah digunakan canvas.toBlob(asinkron)

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
    });
amirnissim
sumber
1
Saya kira Anda kehilangan beberapa informasi dengan itu ... seperti info meta itu seperti mengubah gambar apa pun menjadi png, jadi ini bukan hasil yang sama, juga ini hanya berfungsi untuk gambar
Endless
Saya kira Anda bisa memperbaikinya dengan mengekstraksi tipe gambar image/jpgdari string base64 dan kemudian meneruskannya sebagai parameter kedua ke dalam toBlobfungsi sehingga hasilnya adalah tipe yang sama. Selain itu saya pikir ini sempurna - menghemat 30% dari lalu lintas dan ruang disk Anda di server (dibandingkan dengan base64) dan bekerja dengan baik bahkan dengan PNG transparan.
icl7126
1
Fungsi macet dengan gambar lebih besar dari 2MB ... di Android saya mendapatkan pengecualian: android.os.TransactionTooLarge
Ruben
14

Lihat contoh ini: 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);

Arcaela
sumber
Penjelasan akan diurutkan.
Peter Mortensen
9

Saya perhatikan bahwa Internet Explorer 11 menjadi sangat lambat ketika mengiris data seperti yang disarankan Jeremy. Ini berlaku untuk Chrome, tetapi Internet Explorer tampaknya memiliki masalah ketika meneruskan data yang diiris ke Blob-Constructor. Di komputer saya, melewatkan 5 MB data membuat Internet Explorer macet dan konsumsi memori melewati atap. Chrome menciptakan gumpalan dalam waktu singkat.

Jalankan kode ini untuk perbandingan:

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

Jadi saya memutuskan untuk memasukkan kedua metode yang dijelaskan oleh Jeremy dalam satu fungsi. Kredit pergi kepadanya untuk ini.

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;
    }
}
martinoss
sumber
Terima kasih sudah memasukkan ini. Dengan pembaruan terbaru ke IE11 (antara 5/2016 dan 8/2016), menghasilkan gumpalan dari array mulai mengambil jumlah ram yang lebih besar. Dengan mengirimkan satu Uint8Array ke konstruktor blog, ia menggunakan hampir tidak ada ram dan benar-benar menyelesaikan proses.
Andrew Vogel
Meningkatkan ukuran irisan dalam sampel uji dari 1K ke 8..16K secara signifikan mengurangi waktu di IE. Pada PC asli saya kode membutuhkan waktu 5 hingga 8 detik, kode dengan blok 8K hanya membutuhkan waktu 356ms, dan 225ms untuk blok 16K
Victor
5

Jika Anda dapat menambahkan satu ketergantungan pada proyek Anda, ada blob-utilpaket npm yang hebat yang menyediakan base64StringToBlobfungsi praktis . Setelah ditambahkan ke Anda, package.jsonAnda dapat menggunakannya seperti ini:

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...
gabriele.genta
sumber
5

Untuk semua pecinta salin-tempel di luar sana seperti saya, berikut ini adalah fungsi unduhan matang yang berfungsi di Chrome, Firefox dan 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);
    });
}
Eyup Yusein
sumber
yang createObjectURLtidak menerima argumen 2 ...
tak berujung
3

Saya memposting cara yang lebih deklaratif untuk sinkronisasi konversi Base64. Walaupun async fetch().blob()sangat rapi dan saya sangat menyukai solusi ini, ia tidak bekerja di Internet Explorer 11 (dan mungkin Edge - saya belum menguji yang ini), bahkan dengan polyfill - lihat komentar saya di Endless ' posting untuk lebih jelasnya.

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

Bonus

Jika Anda ingin mencetaknya, Anda dapat melakukan sesuatu seperti:

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

Bonus x 2 - Membuka file BLOB di tab baru untuk Internet Explorer 11

Jika Anda dapat melakukan preprocessing dari string Base64 di server Anda dapat mengeksposnya di bawah beberapa URL dan menggunakan tautan di printJS:)

ayah
sumber
2

Berikut ini adalah kode TypeScript saya yang dapat dikonversi dengan mudah menjadi JavaScript dan dapat Anda gunakan

/**
 * 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 });
}
KAUSHIK PARMAR
sumber
4
Sementara cuplikan kode ini mungkin solusinya, termasuk penjelasan sangat membantu untuk meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, dan orang-orang itu mungkin tidak tahu alasan untuk saran kode Anda.
Johan
2
Juga, mengapa Anda BERTERIAK pada komentar?
canbax
4
Typescript codeKode Anda hanya memiliki tipe TUNGGAL dan tipe itu any. Seperti kenapa repot-repot ??
zoran404
0

Metode dengan mengambil adalah solusi terbaik, tetapi jika ada yang perlu menggunakan metode tanpa mengambil maka ini dia, seperti yang disebutkan sebelumnya tidak bekerja untuk saya:

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