JavaScript'te Base64 dizesinden BLOB oluşturma


447

Bir dizede Base64 kodlu ikili veri var:

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

blob:Bu verileri içeren bir URL oluşturmak ve kullanıcıya görüntülemek istiyorum:

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

window.location = blobUrl;

BLOB'u nasıl oluşturacağımı anlayamadım.

Bazı durumlarda data:bunun yerine bir URL kullanarak bunu önleyebilirim :

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

window.location = dataUrl;

Ancak, çoğu durumda data:URL'ler oldukça büyüktür.


Base64 dizesini JavaScript'teki bir BLOB nesnesine nasıl çözebilirim?

Yanıtlar:


790

atobFonksiyonu ikili her veri byte için bir karakter ile yeni bir dizeye Base64 ile kodlanmış dize deşifre edecek.

const byteCharacters = atob(b64Data);

Her karakterin kod noktası (charCode) baytın değeri olacaktır. .charCodeAtDizedeki her karakter için yöntemi kullanarak bunu uygulayarak bir bayt değerleri dizisi oluşturabiliriz .

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

Bu bayt değerleri dizisini yapıcıya ileterek gerçek bir yazılan bayt dizisine dönüştürebilirsiniz Uint8Array.

const byteArray = new Uint8Array(byteNumbers);

Bu da bir diziye sarılarak ve yapıcıya iletilerek bir BLOB'a dönüştürülebilir Blob.

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

Yukarıdaki kod çalışır. Bununla birlikte, performans bir byteCharacterskerede değil, daha küçük dilimlerle işlenerek biraz geliştirilebilir . Benim kaba test benim 512 bayt iyi bir dilim boyutu gibi görünüyor. Bu bize aşağıdaki işlevi verir.

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;

Tam Örnek:


6
Merhaba Jeremy. Web uygulamamızda bu kodu kullandık ve indirilen dosyalar daha büyük olana kadar herhangi bir soruna neden olmadı. Bu nedenle, kullanıcılar 100mb'den daha büyük dosyaları indirmek için Chrome veya IE kullanırken üretim sunucusunda kilitlenmelere ve çökmelere neden oldu. IE'de aşağıdaki satırda "var byteNumbers = new Array (slice.length)" bellek istisnası oluşturduğunu gördük. Ancak, krom, aynı soruna neden olan for döngüsü oldu. Bu soruna uygun bir çözüm bulamadık, sonra window.open kullanarak doğrudan dosya indirmeye geçtik. Burada yardım edebilir misiniz?
Akshay Raut

Bir video dosyasını yerel tepki olarak base64'e dönüştürmek için herhangi bir yöntem var mı? Bunu bir görüntü dosyasıyla yapmayı başardım, ancak videolar için aynı çözümü bulamadım. Bağlantılar da yararlı olacaktır.
Diksha235

Yani atob () tarafından döndürülen dizede 0'ları saklamakta sorun yok mu?
wcochran

Bu benim için Chrome ve Firefox'taki bazı lekeler için işe yaramadı, ancak kenarda çalıştı: /
Gragas Incoming

benim için çalıştı. onun bir ** JSON Ayrıştırma hatası atar: Tanınmayan toke '<' ** ben bir görüntü koyarak bir tarayıcı koyarak base64 dize kontrol etti. Yardıma ihtiyacım var.
Aman Deep

273

İşte herhangi bir bağımlılık veya kitaplık olmadan daha minimal bir yöntem.
Yeni getirme API'sını gerektirir. ( Kullanabilir miyim? )

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

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

Bu yöntemle kolayca bir ReadableStream, ArrayBuffer, text ve JSON alabilirsiniz.

İşlev olarak:

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

Jeremy'nin ES6 senkronizasyon sürümüne yönelik basit bir performans testi yaptım.
Senkronizasyon sürümü kullanıcı arayüzünü bir süre engeller. devtool'u açık tutmak getirme performansını yavaşlatabilir


1
Base64 kodlu dizenin boyutu büyükse, yine de Opera'daki URI boyutları için sınır olan 665536 karakterden daha büyük olduğunu varsayalım mı?
Daniel Kats

1
Bilmiyorum, bunun adres çubuğu için bir sınır olabileceğini biliyorum, ancak AJAX ile bir şeyler yapmak bir render yapmak zorunda olmadığından bir istisna olabilir. Test etmelisin. Eğer beni ilk etapta asla base64 dizesini almış olsaydım. Bunun kötü bir uygulama olduğunu düşünmek, kod çözme ve kodlama için daha fazla bellek ve zaman alır. createObjectURLyerine readAsDataURLçok daha iyidir. Ve ajax kullanarak dosya yüklerseniz, FormDatayerine seçin JSONveya canvas.toBlobyerine kullanıntoDataURL
Endless

7
Hatta daha iyi:await (await fetch(imageDataURL)).blob()
icl7126 10:18

3
en son tarayıcıyı hedeflerseniz emin olun. Ancak bu, işlevin de zaman uyumsuz bir işlevin içinde olmasını gerektirir. Konuşmak ... await fetch(url).then(r=>r.blob())sıralayıcı
Endless

2
Çok düzgün bir çözüm, ama benim bilgime göre IE (polyfill ofc ile) Access is denied.hata nedeniyle çalışmaz . Sanırım fetchblob url altında blob - aynı şekilde URL.createObjectUrl- yani 11 üzerinde çalışmaz. referans . Belki IE11 ile getirme için bazı geçici çözüm var? Diğer senkronizasyon çözümlerinden çok daha iyi görünüyor :)
Papi

72

Optimize edilmiş (ancak daha az okunabilir) uygulama:

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
Baytların lekelere dilimlenmesi için herhangi bir neden var mı? Kullanmazsam herhangi bir dezavantaj veya risk var mı?
Alfred Huang

Ionic 1 / Angular 1 ile Android'de harika çalışıyor. Dilim gerekiyor, aksi takdirde OOM (Android 6.0.1) ile karşılaşıyorum.
Jürgen 'Kashban' Wahlmann

4
Sadece örnek olarak IE 11 ve Chrome'da bir kurumsal ortamda herhangi bir belge türüyle sorunsuz çalışabilirim.
santos

Bu fantastik. Teşekkür ederim!
elliotwesoff

Bir açıklama yapılabilir. Örneğin, neden daha yüksek performansa sahip?
Peter Mortensen

19

Tüm tarayıcı desteği için, özellikle Android'de, belki bunu ekleyebilirsiniz:

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

Teşekkürler, ancak doğru okuduğumda yukarıda yazdığınız kod snippet'inde İKİ sorun var: (1) Diğer sondaki catch () içindeki kod try () içindeki orijinal kodla aynı ise: "blob = new Blob (byteArrays, {type: contentType}) "... Orijinal istisnadan sonra aynı kodu tekrarlamayı neden önerdiğinizi bilmiyorum? ... (2) BlobBuilder.append () bayt dizilerini DEĞİL, ArrayBuffer'ı kabul edemez. Bu nedenle, girdi bayt-dizileri bu API kullanılmadan önce ArrayBuffer'a dönüştürülmelidir. REF: developer.mozilla.org/tr-TR/docs/Web/API/BlobBuilder
Panini Luncher

14

Görüntü verileri için kullanımını daha basit buluyorum canvas.toBlob(eşzamansız)

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
Sanırım bununla bazı bilgileri kaybedersiniz ... meta bilgi gibi herhangi bir görüntüyü png'ye dönüştürmek gibi, bu yüzden aynı sonuç değil, aynı zamanda sadece görüntüler için çalışıyor
Endless

image/jpgBase64 dize görüntü türü ayıklamak ve daha sonra toBlobsonuç aynı tür böylece işlevine ikinci bir parametre olarak geçirerek geliştirmek olabilir sanırım . Bunun dışında bunun mükemmel olduğunu düşünüyorum -% 30 trafik ve sunucudaki disk alanından tasarruf (base64 ile karşılaştırıldığında) ve şeffaf PNG ile bile güzel çalışıyor.
icl7126

1
Fonksiyon 2MB'den daha büyük görüntülerle çöküyor ... Android'de istisna alıyorum: android.os.TransactionTooLarge
Ruben

14

Bu örneğe bakın: 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);


Bir açıklama yapılabilir.
Peter Mortensen

9

Jeremy'nin önerdiği gibi verileri dilimlerken Internet Explorer 11'in inanılmaz derecede yavaşladığını fark ettim. Bu Chrome için geçerlidir, ancak Internet Explorer'ın dilimlenmiş verileri Blob-Constructor öğesine iletirken bir sorunu var gibi görünüyor. Makinemde 5 MB veri iletilmesi Internet Explorer'ın çökmesine neden oluyor ve bellek tüketimi tavandan geçiyor. Chrome, bloğu en kısa sürede oluşturur.

Karşılaştırma için bu kodu çalıştırın:

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

Bu yüzden Jeremy tarafından açıklanan her iki yöntemi de bir fonksiyona dahil etmeye karar verdim. Kredi bunun için ona gider.

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

Bunu eklediğiniz için teşekkür ederim. IE11'e yakın zamanda yapılan bir güncelleme ile (5/2016 ve 8/2016 arasında), dizilerden lekeler üretmek büyük miktarda daha fazla koç almaya başladı. Blog yapıcısına tek bir Uint8Array göndererek, neredeyse hiçbir koç kullanmamış ve işlemi gerçekten tamamlamıştır.
Andrew Vogel

Test örneğinde dilim boyutunun 1K'dan 8..16K'ya çıkarılması IE'de zamanı önemli ölçüde azaltır. Bilgisayarımda orijinal kod 5 ila 8 saniye sürdü, 8K bloklu kod sadece 356ms ve 16K bloklar için 225ms
Victor

6

Benim gibi tüm kopyala yapıştırma severler için, Chrome, Firefox ve Edge'de çalışan pişmiş bir indirme işlevi:

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

createObjectURL2. bir argüman ... kabul etmiyoruz
Sonsuz

5

Projenize bir bağımlılık blob-utilkatabiliyorsanız , kullanışlı bir base64StringToBlobişlev sağlayan harika npm paketi var . Bir kez eklendiğinde package.jsonşöyle kullanabilirsiniz:

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

3

Base64 dönüştürmek için daha bildirici bir senkronizasyon yolu gönderiyorum. Zaman uyumsuz iken fetch().blob()çok bu çözümü gibi çok düzgün ve ben, bu Internet Explorer 11 üzerinde çalışmaya değil (ve muhtemelen Kenar - Bunu test değil), hatta Polyfill ile - benim yorumum bakmak Sonsuz daha fazla bilgi için gönderin .

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

Bonus

Yazdırmak istiyorsanız, aşağıdakine benzer bir şey yapabilirsiniz:

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 - Internet Explorer 11 için yeni sekmede bir BLOB dosyası açma

Base64 dizesinin sunucuda bazı ön işlemlerini yapabiliyorsanız, onu bir URL altında gösterebilir ve içindeki bağlantıyı kullanabilirsiniz printJS:)


2

Aşağıda kolayca JavaScript'e dönüştürülebilen TypeScript kodum var ve

/**
 * 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
Bu kod snippet'i çözüm olsa da, bir açıklama da dahil olmak üzere mesajınızın kalitesini artırmaya yardımcı olur. Gelecekte okuyucular için soruyu cevapladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceğini unutmayın.
Johan

2
Ayrıca, neden yorumlarda bağırıyorsunuz?
canbax

4
Kişisel Typescript codekod yalnızca bir TEK türü vardır ve bu türüdür any. Neden rahatsız bile?
zoran404

0

Getirme yöntemi en iyi çözümdür, ancak herkes getirme olmadan bir yöntem kullanması gerekiyorsa, o zaman burada, daha önce belirtilenler benim için çalışmadığı için:

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 });
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.