Tworzenie BLOBa z ciągu Base64 w JavaScript


447

Mam dane binarne zakodowane w standardzie Base64 w ciągu:

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

Chciałbym utworzyć blob:adres URL zawierający te dane i wyświetlić go użytkownikowi:

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

window.location = blobUrl;

Nie byłem w stanie dowiedzieć się, jak utworzyć BLOB.

W niektórych przypadkach mogę tego uniknąć, używając data:adresu URL:

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

window.location = dataUrl;

Jednak w większości przypadków data:adresy URL są zbyt duże.


Jak mogę zdekodować ciąg Base64 na obiekt BLOB w JavaScript?

Odpowiedzi:


790

atobFunkcja dekodowania Base64 zakodowany ciąg do nowego napisu z charakterem każdego bajta danych binarnych.

const byteCharacters = atob(b64Data);

Punkt kodowy każdego znaku (charCode) będzie wartością bajtu. Możemy utworzyć tablicę wartości bajtów, stosując tę .charCodeAtmetodę za pomocą metody dla każdego znaku w ciągu.

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

Możesz przekonwertować tablicę wartości bajtów na tablicę bajtów o typie rzeczywistym, przekazując ją do Uint8Arraykonstruktora.

const byteArray = new Uint8Array(byteNumbers);

To z kolei można przekonwertować na BLOB, zawijając go w tablicę i przekazując do Blobkonstruktora.

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

Powyższy kod działa. Jednak wydajność można nieco poprawić, przetwarzając byteCharactersmniejsze kawałki, a nie wszystkie naraz. W moich testach zgrubnych 512 bajtów wydaje się mieć dobry rozmiar wycinka. To daje nam następującą funkcję.

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;

Pełny przykład:


6
Cześć Jeremy. Mamy ten kod w naszej aplikacji internetowej i nie powodował żadnych problemów, dopóki pobierane pliki nie miały większych rozmiarów. Powodowało to zawieszanie się i awarie na serwerze produkcyjnym, gdy użytkownicy używali przeglądarki Chrome lub IE do pobierania plików większych niż 100 MB. Odkryliśmy, że w następnej linii IE pojawił się wyjątek pamięci „var byteNumbers = new Array (slice.length)”. Jednak w przypadku Chrome była to pętla for powodująca ten sam problem. Nie mogliśmy znaleźć odpowiedniego rozwiązania tego problemu, a następnie przeszliśmy do bezpośredniego pobierania plików za pomocą window.open. Czy możesz tu pomóc?
— Akshay Raut

Czy jest to jakaś metoda konwersji pliku wideo na base64 w reakcji native? Udało mi się to zrobić z plikiem obrazu, ale nie znalazłem rozwiązania tego samego dla filmów. Linki będą pomocne lub również rozwiązanie.
— Diksha235

Więc nie ma problemów z przechowywaniem zer w ciągu zwracanym przez atob ()?
— wcochran

to nie działało dla mnie dla niektórych obiektów blob w Chrome i Firefox, ale działało na krawędzi: /
— Gragas Incoming

pracował dla mnie. zgłasza ** Błąd analizy JSON: Nierozpoznany toke „<” ** Sprawdziłem ciąg base64, uruchamiając przeglądarkę, która tworzy obraz. potrzebuję pomocy.
— Aman Deep

273

Oto bardziej minimalna metoda bez żadnych zależności i bibliotek.
Wymaga nowego interfejsu API pobierania. ( Czy mogę tego użyć? )

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

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

Za pomocą tej metody można również łatwo uzyskać ReadableStream, ArrayBuffer, tekst i JSON.

Jako funkcja:

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

Zrobiłem prosty test wydajności w stosunku do wersji synchronizacji ES6 Jeremy'ego.
Wersja synchronizacji blokuje interfejs użytkownika na chwilę. utrzymywanie devtoola otwartego może spowolnić działanie pobierania


1
Czy to nadal zadziała, jeśli rozmiar łańcucha zakodowanego w base64 jest duży, powiedzmy większy niż 665536 znaków, co stanowi limit rozmiarów URI w Operze?
— Daniel Kats

1
Nie wiem, wiem, że może to być ograniczenie paska adresu, ale robienie rzeczy z AJAX może być wyjątkiem, ponieważ nie musi być renderowane. Musisz to przetestować. Gdyby to było miejsce, w którym nigdy nie zdobyłbym łańcucha base64. Myślenie, że to zła praktyka, zajmuje więcej pamięci i czasu na dekodowanie i kodowanie. createObjectURLzamiast readAsDataURLjest na przykład znacznie lepszy. A jeśli przesyłasz pliki za pomocą ajax, wybierz FormDatazamiast JSONlub użyj canvas.toBlobzamiasttoDataURL
— Endless

7
Jeszcze lepiej jak w linii:await (await fetch(imageDataURL)).blob()
— icl7126,

3
jasne, jeśli celujesz w najnowszą przeglądarkę. Ale to wymaga, aby funkcja również znajdowała się w funkcji asynchronicznej. Mówiąc o ... await fetch(url).then(r=>r.blob())jest sorter
— Endless

2
Bardzo fajne rozwiązanie, ale według mojej wiedzy nie będzie działać z IE (z polyfill ofc) z powodu Access is denied.błędu. Wydaje mi się, że fetchujawnia obiekt blob pod adresem URL obiektu blob - w ten sam sposób URL.createObjectUrl- który nie działa na ie11. odniesienie . Może jest jakieś obejście, aby użyć pobierania z IE11? Wygląda znacznie lepiej niż inne rozwiązania synchronizacji :)
— Papi

72

Zoptymalizowana (ale mniej czytelna) implementacja:

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
Czy istnieje jakiś powód, aby pokroić bajty na obiekty BLOB? Jeśli nie używam, czy jest jakaś wada lub ryzyko?
— Alfred Huang

Działa świetnie na Androidzie z Ionic 1 / Angular 1. Kromka jest wymagana, w przeciwnym razie uruchomię OOM (Android 6.0.1).
— Jürgen 'Kashban' Wahlmann

4
Jedyny przykład, który mogłem płynnie pracować z dowolnym typem dokumentu w środowisku korporacyjnym zarówno w IE 11, jak i Chrome.
— Santos

To jest fantastyczne. Dziękuję Ci!
— elliotwesoff

Wytłumaczenie byłoby w porządku. Np. Dlaczego ma wyższą wydajność?
— Peter Mortensen

19

W przypadku wszystkich przeglądarek, zwłaszcza na Androidzie, możesz dodać:

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

Dzięki, ale we fragmencie kodu, który napisałeś powyżej, są dwa problemy, jeśli poprawnie go przeczytałem: (1) Kod w catch () w ostatnim przypadku - jeśli jest taki sam jak oryginalny kod w try (): "blob = nowy obiekt Blob (byteArrays, {type: contentType}) „... Nie wiem, dlaczego sugerujesz powtarzanie tego samego kodu po oryginalnym wyjątku? ... (2) BlobBuilder.append () NIE akceptuje tablic bajtów, ale ArrayBuffer. Tak więc wejściowe tablice bajtów muszą zostać przekształcone dalej w ArrayBuffer przed użyciem tego interfejsu API.
— ODNIESIENIE

14

W przypadku danych obrazu korzystanie z niego jest prostsze canvas.toBlob(asynchroniczne)

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
Myślę, że tracisz z tym trochę informacji ... jak meta info to jak konwersja dowolnego obrazu do formatu png, więc nie jest to ten sam wynik, to także działa tylko dla obrazów
— Endless

Myślę, że można to poprawić, wyodrębniając typ obrazu image/jpgz ciągu base64, a następnie przekazując go jako drugi parametr do toBlobfunkcji, aby wynik był tego samego typu. Poza tym uważam, że jest to idealne - pozwala zaoszczędzić 30% ruchu i miejsca na dysku na serwerze (w porównaniu do base64) i działa dobrze nawet z przezroczystym PNG.
— icl7126

1
Funkcja ulega awarii z obrazami większymi niż 2 MB ... w Androidzie otrzymuję wyjątek: android.os.TransactionTooLarge
— Ruben

14

Zobacz ten przykład: 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);


Wytłumaczenie byłoby w porządku.
— Peter Mortensen

9

Zauważyłem, że Internet Explorer 11 staje się niesamowicie wolny podczas krojenia danych, jak sugerował Jeremy. Dotyczy to przeglądarki Chrome, ale wydaje się, że Internet Explorer ma problem z przesyłaniem podzielonych danych do Konstruktora obiektów blob. Na moim komputerze przekazanie 5 MB danych powoduje awarię programu Internet Explorer i zużycie pamięci spada. Chrome tworzy kroplę w mgnieniu oka.

Uruchom ten kod, aby porównać:

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

Dlatego postanowiłem zawrzeć obie metody opisane przez Jeremy'ego w jednej funkcji. Podziękowania należą się mu za to.

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

Dziękujemy za uwzględnienie tego. Wraz z ostatnią aktualizacją IE11 (między 5/2016 a 8/2016), generowanie obiektów blob z tablic zaczęło przyjmować o wiele większą ilość pamięci RAM. Wysyłając pojedynczy Uint8Array do konstruktora blogów, prawie nie używał pamięci RAM i faktycznie zakończył proces.
— Andrew Vogel,

Zwiększenie rozmiaru wycinka w próbce testowej z 1K do 8..16K znacznie skraca czas w IE. Na moim komputerze oryginalny kod trwał od 5 do 8 sekund, kod z blokami 8K zajął tylko 356ms, a 225ms dla bloków 16K
— Victor

6

Dla wszystkich miłośników kopiowania i wklejania, takich jak ja, oto gotowana funkcja pobierania, która działa w Chrome, Firefox i 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);
    });
}

createObjectURLnie akceptują 2nd argumentu ...
— Kompletne

5

Jeśli możesz znieść dodanie jednej zależności do swojego projektu, istnieje świetny blob-utilpakiet npm, który zapewnia przydatną base64StringToBlobfunkcję. Po dodaniu package.jsonmożesz użyć go w następujący sposób:

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

Publikuję bardziej deklaratywny sposób synchronizacji konwersji Base64. Chociaż async fetch().blob()jest bardzo schludny i bardzo podoba mi się to rozwiązanie, nie działa on w przeglądarce Internet Explorer 11 (i prawdopodobnie Edge - nie testowałem tego), nawet z polifillem - spójrz na mój komentarz do Endless ' opublikuj po więcej szczegółów.

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

Premia

Jeśli chcesz go wydrukować, możesz zrobić coś takiego:

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 - Otwarcie pliku BLOB w nowej karcie dla Internet Explorera 11

Jeśli możesz wykonać wstępne przetwarzanie ciągu Base64 na serwerze, możesz udostępnić go pod jakimś adresem URL i użyć linku printJS:)


2

Poniżej znajduje się mój kod TypeScript, który można łatwo przekonwertować na JavaScript i można z niego korzystać

/**
 * 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
Ten fragment kodu może być rozwiązaniem, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
— Johan

2
A także dlaczego krzyczycie na komentarze?
— canbax,

4
Twój Typescript codekod ma tylko POJEDYNCZY typ, a ten typ to any. Na przykład, dlaczego w ogóle przeszkadza
— zoran404

0

Metoda z pobieraniem jest najlepszym rozwiązaniem, ale jeśli ktoś musi użyć metody bez pobierania, oto ona, ponieważ wspomniane wcześniej nie działały dla mnie:

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 });
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.