Створення BLOB з рядка Base64 в JavaScript


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-адреси надмірно великі.


Як я можу розшифрувати рядок Base64 до об'єкта BLOB в JavaScript?

Відповіді:


790

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
Привіт, Джеремі. Цей код у нас був у нашій веб-програмі, і він не спричинив жодних проблем, поки завантажувані файли не мали більшого розміру. Тож це спричинило зависання та збої на виробничому сервері, коли користувачі використовували Chrome або IE для завантаження файлів розміром більше 100 Мб. Ми виявили, що наступний рядок в IE збільшує виняток пам'яті "var byteNumbers = новий масив (slice.length)". Однак у хромі це саме цикл викликав ту саму проблему. Не вдалося знайти належного вирішення цієї проблеми, тоді ми перейшли до безпосередньо завантаження файлів за допомогою window.open. Чи можете ви надати тут трохи допомоги?
Акшай Раут

Це будь-який спосіб перетворення відеофайлу в base64 у реагує на рідну мову? Мені вдалося це зробити з файлом зображень, але не знайшов рішення для того ж для відео. Посилання будуть корисними або також рішенням.
Дикша235

Тож немає проблем із збереженням 0 у рядку, поверненому atob ()?
wcochran

для мене це не спрацювало з деякими краплями на Chrome і Firefox, але працювало на межі: /
Gragas Incoming

зробив для мене роботу. його кидає ** JSON Помилка синтаксичного аналізу: Нерозпізнаний токев '<' ** Я перевірив рядок base64, помістивши в браузер, він створює зображення. потрібна допомога.
Аман Глибокий

273

Ось більш мінімальний метод без будь-яких залежностей і бібліотек.
Для цього потрібен новий 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())

Я зробив просту перевірку працездатності щодо версії синхронізації Джеремі ES6.
Версія синхронізації деякий час блокуватиме інтерфейс користувача. утримання відкритого інструменту devtool може уповільнити продуктивність


1
Чи все-таки це буде працювати, якщо розмір рядка, кодованого base64, великий, скажімо, більший за 665536 символів, що є обмеженням для розмірів URI в Opera?
Даніель Кац

1
Не знаю, я знаю, що це може бути обмеженням адресної панелі, але робити речі з AJAX може бути винятком, оскільки її не потрібно виводити. Ви повинні це протестувати. Якби там, де я, я б ніколи не отримав рядок base64 в першу чергу. Думаючи, що це погана практика, для дешифрування та кодування потрібно більше пам’яті та часу. createObjectURLзамість readAsDataURLнабагато краще, наприклад. А якщо ви завантажуєте файли за допомогою ajax, вибирайте FormDataзамість JSONабо використовуйте canvas.toBlobзамістьtoDataURL
Нескінченний

7
Навіть краще, як inline:await (await fetch(imageDataURL)).blob()
icl7126

3
звичайно, якщо ви орієнтуєтесь на останній веб-переглядач. Але це вимагає, щоб функція була і всередині функції асинхронізації. Якщо говорити про ... await fetch(url).then(r=>r.blob())це сортування
Нескінченний

2
Дуже акуратне рішення, але, за моїми знаннями, не працюватиме з IE (з polyfill ofc) через Access is denied.помилку. Я думаю, fetchвиставляє крапку під URL-адресою URL.createObjectUrlблобу - так само, як це робиться - що не буде працювати на ie11. довідник . Можливо, є якесь вирішення для використання завантаження з IE11? Це виглядає набагато краще, ніж інші рішення синхронізації :)
Папі

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
Чи є якась причина для нарізки байтів на краплі? Якщо я не користуюся, чи є недолік чи ризик?
Альфред Хуанг

Відмінно працює на Android з Ionic 1 / Angular 1. Фрагмент необхідний, інакше я наткнувся на OOM (Android 6.0.1).
Юрген 'Кашбан' Вальман

4
Тільки приклад там я міг змусити безперешкодно працювати з будь-яким типом документа у корпоративній середовищі як IE 11, так і Chrome.
santos

Це фантастично. Дякую!
elliotwesoff

Пояснення було б в порядку. Наприклад, чому вона має більш високу продуктивність?
Пітер Мортенсен

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) Код у програмі catch () на останньому іншому - якщо такий же, як і вихідний код у try (): "blob = новий Blob (byteArrays, {type: contentType}) "... Не знаю, чому ви пропонуєте повторити той самий код після оригінального винятку? ... (2) BlobBuilder.append () НЕ може приймати байтові масиви, але ArrayBuffer. Отже, вхідні байтові масиви повинні бути перетворені далі у свій ArrayBuffer перед використанням цього API. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Паніні Ланчер

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, тому це не той самий результат, також це працює лише для зображень
нескінченний

Я думаю, ви могли б покращити його, витягнувши тип зображення image/jpgз рядка base64, а потім передайте його як другий параметр у toBlobфункцію, щоб результат був однаковим. Крім цього, я вважаю, що це ідеально - це економить 30% трафіку та вашого дискового простору на сервері (порівняно з base64), і це добре працює навіть із прозорим PNG.
icl7126

1
Функція вибивається із зображеннями розмірами більше 2 Мб ... в Android я отримую виняток: android.os.TransactionTooLarge
Рубен

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


Пояснення було б в порядку.
Пітер Мортенсен

9

Я помітив, що Internet Explorer 11 стає надзвичайно повільним під час нарізки даних, як запропонував Джеремі. Це стосується Chrome, але, здається, у Internet Explorer виникає проблема при передачі нарізаних даних Blob-Constructor. На моїй машині передача даних 5 Мб призводить до збоїв у роботі Internet Explorer, а споживання пам'яті переживає дах. Chrome створює крапку за короткий час.

Запустіть цей код для порівняння:

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

Тому я вирішив включити обидва методи, описані Джеремі, в одну функцію. Кредити йдуть йому за це.

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) генерування крапель з масивів почало приймати на більший об’єм таран. Відправивши єдиний Uint8Array в конструктор блогу, він майже не використовував таран і фактично завершив процес.
Андрій Фогель

Збільшення розміру зрізів у досліджуваному зразку від 1K до 8..16K значно скорочує час в IE. На моєму комп’ютері оригінальний код зайняв від 5 до 8 секунд, код з 8K блоками зайняв лише 356 мс, а 225мм для 16К блоків
Віктор,

6

Для всіх любителів копіювальної пасти, як я, ось функція завантаження, яка працює в 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не приймає 2 - й аргументу ...
Нескінченний

5

Якщо ви можете додати одну залежність до свого проекту, існує чудовий blob-utilпакет npm, який забезпечує зручну 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...

3

Я публікую декларативний спосіб синхронізації перетворення Base64. Хоча асинхрон fetch().blob()дуже акуратний і мені дуже подобається це рішення, він не працює в 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 - відкриття файлу BLOB на новій вкладці для Internet Explorer 11

Якщо ви можете виконати деяку попередню обробку рядка 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код має єдиний тип і такий тип є any. Як, чому навіть турбувати ??
zoran404

0

Метод із функцією fetch - найкраще рішення, але якщо комусь потрібно використовувати метод без вибору, то ось він, як ті, про які було сказано раніше, не працювали для мене:

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 });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.