Создание 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?

Джереми Бэнкс
источник

Ответы:

792

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 = new Array (slice.length)". Однако в chrome это был цикл for, вызывающий ту же проблему. Мы не смогли найти правильное решение этой проблемы, затем мы перешли к прямой загрузке файлов с помощью window.open. Можете ли вы помочь здесь?
Акшай Раут
Это какой-либо метод для преобразования видео файла в base64 в реагировать родной? Мне удалось сделать это с помощью файла изображения, но я не нашел такого же решения для видео. Ссылки будут полезны или решение также.
Diksha235
Таким образом, нет проблем с сохранением 0 в строке, возвращаемой atob ()?
wcochran
у меня это не сработало для некоторых блобов в Chrome и Firefox, но сработало на краю: /
Gragas Incoming
работал на меня. он выдает ошибку ** JSON Parse: нераспознанный ток '<' ** я проверил строку base64, поместив в браузер изображение, которое он создает. нужна помощь.
Аман Глубокий
273

Вот более минимальный метод без каких-либо зависимостей или библиотек.
Требуется новый API выборки. ( Могу ли я использовать это? )

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

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

С помощью этого метода вы также можете легко получить ReadableStream, ArrayBuffer, text и JSON.

Как функция:

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

Я сделал простой тест производительности для версии синхронизации Jeremy ES6.
Синхронизированная версия на некоторое время заблокирует пользовательский интерфейс. держать devtool открытым может снизить производительность выборки

Бесконечный
источник
1
Будет ли это работать, если размер строки в кодировке base64 большой, скажем, больше 665536 символов, что является пределом для размеров URI в Opera?
Даниэль Кац
1
Не знаю, я знаю, что это может быть ограничение на адресную строку, но работа с AJAX может быть исключением, так как его не нужно отображать. Вы должны проверить это. Если бы я был там, где бы я был, я бы никогда не получил строку base64. Думая, что это плохая практика, требует больше памяти и времени для декодирования и кодирования. createObjectURLа readAsDataURLне намного лучше например. И если вы загружаете файлы, используя ajax, выберите FormDataвместо JSONили используйте canvas.toBlobвместоtoDataURL
Endless
7
Еще лучше, как в await (await fetch(imageDataURL)).blob()
строке
3
конечно, если вы нацелены на последний браузер. Но для этого требуется, чтобы функция была внутри асинхронной функции. Говоря о ... await fetch(url).then(r=>r.blob())сортируется
бесконечно
2
Очень аккуратное решение, но, насколько мне известно, не будет работать с IE (с polyfill ofc) из-за Access is denied.ошибки. Я полагаю, что fetchBLOB- URL.createObjectUrlобъект выставляется под URL-адресом BLOB-объекта - таким же образом , что и не будет работать на ie11. ссылка . Может быть, есть какой-нибудь способ использовать fetch с IE11? Это выглядит намного лучше, чем другие решения для синхронизации :)
Papi
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 });
}
Bacher
источник
2
Есть ли причина разрезать байты на капли? Если я не пользуюсь, есть ли недостаток или риск?
Альфред Хуанг,
Отлично работает на Android с Ionic 1 / Angular 1. Требуется срез, в противном случае я запускаю OOM (Android 6.0.1).
Юрген Кашбан Вальман
4
Единственный пример, из которого я мог бы беспрепятственно работать с любым типом документов в корпоративной среде в IE 11 и Chrome.
Сантос
Это фантастика. Спасибо!
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 () в последнем else-if совпадает с исходным кодом в try (): "blob = new Blob (byteArrays, {type: contentType}) "" Я не знаю, почему вы предлагаете повторить тот же код после оригинального исключения? ... (2) BlobBuilder.append () не может принимать байтовые массивы, кроме ArrayBuffer. Таким образом, входные байтовые массивы должны быть преобразованы далее в его ArrayBuffer перед использованием этого API. REF: developer.mozilla.org/en-US/docs/Web/API/BlobBuilder
Panini Luncher
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
    });
amirnissim
источник
1
Я думаю, что вы теряете некоторую информацию с этим ... как мета-информация, это как преобразование любого изображения в png, так что это не тот же результат, также это работает только для изображений
Endless
Я думаю, вы могли бы улучшить его, извлекая тип изображения 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);

Arcaela
источник
Объяснение будет в порядке.
Питер Мортенсен
9

Я заметил, что Internet Explorer 11 работает невероятно медленно при разрезании данных, как предложил Джереми. Это верно для Chrome, но Internet Explorer, похоже, испытывает проблемы при передаче нарезанных данных в Blob-конструктор. На моей машине пропуск 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;
    }
}
martinoss
источник
Спасибо, что включили это. С недавним обновлением IE11 (между 5/2016 и 8/2016) генерация больших двоичных объектов из массивов стала занимать гораздо большее количество оперативной памяти. Отправив единственный Uint8Array в конструктор блога, он почти не использовал оперативную память и фактически завершил процесс.
Эндрю Фогель
Увеличение размера среза в тестовом образце с 1 К до 8,16 К значительно сокращает время в IE. На моем ПК оригинальный код занимал от 5 до 8 секунд, код с блоками 8K занимал всего 356 мс, а 225 мс для блоков 16K
Виктор
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...
gabriele.genta
источник
3

Я публикую более декларативный способ синхронизации Base64 преобразования. Хотя async fetch().blob()очень аккуратный, и мне очень нравится это решение, оно не работает в Internet Explorer 11 (и, возможно, Edge - я его не тестировал), даже с polyfill - взгляните на мой комментарий к 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:)

Papi
источник
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 - лучшее решение, но если кому-то нужно использовать метод без 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 });
}
Акшай
источник