Преобразуйте URI данных в файл, затем добавьте в FormData


283

Я пытался повторно внедрить загрузчик изображений HTML5, такой как на сайте Mozilla Hacks , но он работает с браузерами WebKit. Частью задачи является извлечение файла изображения из canvasобъекта и добавление его в объект FormData для загрузки.

Вопрос заключается в том , что в то время как canvasимеет toDataURLфункцию , чтобы возвратить представление файла изображения, объект FormData принимает только файл или объекты Blob из API файлов .

Решение Mozilla использовало следующую функцию только для Firefox canvas:

var file = canvas.mozGetAsFile("foo.png");

... который недоступен в браузерах WebKit. Лучшее решение, о котором я могу подумать, - это найти способ преобразовать URI данных в объект File, который, как мне показалось, может быть частью File API, но я никак не могу найти что-то для этого.

Является ли это возможным? Если нет, есть ли альтернативы?

Спасибо.


Если вы хотите сохранить DataURI изображения на сервере: stackoverflow.com/a/50131281/5466401
Сибин Джон Маттаппаллил

Ответы:


471

Поработав с несколькими вещами, мне удалось выяснить это самому.

Прежде всего, это преобразует dataURI в Blob:

function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {type:mimeString});
}

Отсюда легко добавить данные в форму, чтобы они были загружены в виде файла:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);

29
Почему это всегда происходит ... Вы пытаетесь часами часами решать проблему с SO поисками здесь и там. Тогда вы отправляете вопрос. В течение часа вы получите ответ на другой вопрос. Не то чтобы я жаловался ... stackoverflow.com/questions/9388412/…
syaz

@stoive Я могу создать Blob, но не могли бы вы объяснить, как вы строите POSTили PUTдля S3?
Гаурав Шах

1
@mimo - указывает на базовый объект ArrayBuffer, который затем записывается в BlobBuilderэкземпляр.
Стоив

2
@stoive В таком случае, почему это не bb.append (ia)?
Мимо

4
Спасибо! Это решило мою проблему с небольшой коррекциейvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara

141

BlobBuilder и ArrayBuffer теперь устарели, вот код верхнего комментария, обновленный конструктором Blob:

function dataURItoBlob(dataURI) {
    var binary = atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}

2
Просто идея: array=[]; array.length=binary.length;... array[i]=bina... и т. Д. Итак, массив предварительно выделен. Это спасает push () от необходимости расширять массив каждую итерацию, и мы обрабатываем, возможно, миллионы элементов (= байтов) здесь, так что это важно.
DDS

2
Также терпит неудачу для меня на Safari. @WilliamT. ответ работает для Firefox / Safari / Chrome, хотя.
ObscureRobot

«Двоичный» - это слегка вводящее в заблуждение имя, поскольку это не массив битов, а массив байтов.
Нильс Абилдгаард,

1
"type: 'image / jpeg'" - что если это изображение в формате png ИЛИ, если вы заранее не знаете расширение изображения?
Джаспер

Создать Uint8Array сначала лучше, чем создать массив, а затем преобразовать в Uint8Array.
cuixiping

52

Этот работает в iOS и Safari.

Вам нужно использовать решение ArrayBuffer от Stoive, но вы не можете использовать BlobBuilder, как указывает vava720, так что здесь представлено сочетание обоих.

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], { type: 'image/jpeg' });
}

11
Большой! Но вы все еще можете сохранить динамическую строку MIME, как в решении Стоива, я полагаю? // выделяем компонент mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
По заданному Аронсону

Что с запасным вариантом для iOS6 с префиксом webkit? Как вы справляетесь с этим?
конфил

30

Firefox имеет методы canvas.toBlob () и canvas.mozGetAsFile () .

Но другие браузеры этого не делают.

Мы можем получить dataurl из canvas, а затем преобразовать dataurl в объект blob.

Вот моя dataURLtoBlob()функция. Это очень коротко.

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

Используйте эту функцию с FormData для обработки вашего холста или dataurl.

Например:

var dataurl = canvas.toDataURL('image/jpeg',0.8);
var blob = dataURLtoBlob(dataurl);
var fd = new FormData();
fd.append("myFile", blob, "thumb.jpg");

Также вы можете создать HTMLCanvasElement.prototype.toBlobметод для браузера, не являющегося гекконовым движком.

if(!HTMLCanvasElement.prototype.toBlob){
    HTMLCanvasElement.prototype.toBlob = function(callback, type, encoderOptions){
        var dataurl = this.toDataURL(type, encoderOptions);
        var bstr = atob(dataurl.split(',')[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        var blob = new Blob([u8arr], {type: type});
        callback.call(this, blob);
    };
}

Теперь canvas.toBlob()работает для всех современных браузеров не только Firefox. Например:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);

1
Упомянутый здесь polyfill для canvas.toBlob является правильным способом решения этой проблемы.
Якоб Крузе

2
Я хотел бы подчеркнуть последнее в этом посте: «Теперь canvas.toBlob () работает для всех современных браузеров».
Эрик Симонтон

25

Мой предпочтительный способ - canvas.toBlob ()

Но, в любом случае, здесь есть еще один способ конвертировать base64 в BLOB-объект, используя fetch ^^,

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

fetch(url)
.then(res => res.blob())
.then(blob => {
  var fd = new FormData()
  fd.append('image', blob, 'filename')
  
  console.log(blob)

  // Upload
  // fetch('upload', {method: 'POST', body: fd})
})


что такое fetch и как это актуально?
Рикардо Фрейтас

Fetch - это современный метод ajax, который вы можете использовать вместо того, чтобы XMLHttpRequestURL-адрес данных был просто URL-адресом. Вы можете использовать ajax для извлечения этого ресурса, и у вас есть возможность решить, хотите ли вы использовать его как blob, arraybuffer или text
Endless

1
@Endless 'fetch ()' локальная строка base64 ... действительно умный хак!
Диего Зоракки

1
Имейте в виду , что blob:и data:не везде поддерживается всеми извлекающих реализаций. Мы используем этот подход, так как знаем, что будем иметь дело только с мобильными браузерами (WebKit), но, например, Edge не поддерживает itt: developer.mozilla.org/en-US/docs/Web/API/…
oligofren

19

Благодаря @Stoive и @ vava720 я объединил оба этих способа таким образом, избегая использования устаревших BlobBuilder и ArrayBuffer.

function dataURItoBlob(dataURI) {
    'use strict'
    var byteString, 
        mimestring 

    if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
        byteString = atob(dataURI.split(',')[1])
    } else {
        byteString = decodeURI(dataURI.split(',')[1])
    }

    mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]

    var content = new Array();
    for (var i = 0; i < byteString.length; i++) {
        content[i] = byteString.charCodeAt(i)
    }

    return new Blob([new Uint8Array(content)], {type: mimestring});
}

12

Развивающийся стандарт выглядит как canvas.toBlob (), а не canvas.getAsFile (), как мог догадаться Mozilla.

Я не вижу ни одного браузера, поддерживающего его :(

Спасибо за эту замечательную тему!

Кроме того, любой, кто пытается принять принятый ответ, должен быть осторожен с BlobBuilder, так как я считаю, что поддержка ограничена (и пространством имен):

    var bb;
    try {
        bb = new BlobBuilder();
    } catch(e) {
        try {
            bb = new WebKitBlobBuilder();
        } catch(e) {
            bb = new MozBlobBuilder();
        }
    }

Вы использовали полифилл другой библиотеки для BlobBuilder?


Я использовал Chrome без полифилов и не помню, чтобы сталкивался с пространством имен. Я с нетерпением ожидаю canvas.toBlob()- это кажется гораздо более подходящим, чем getAsFile.
Стоив

1
BlobBuilderпохоже, не рекомендуется в пользуBlob
Sandstrom

BlobBuilder устарел, и этот шаблон ужасен. Лучше было бы: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); вложенные попытки ловли действительно ужасны, и что произойдет, если ни один из сборщиков не будет доступен?
Крис Хэнсон

Как это ужасно? Если выдается исключение и 1) BlobBuilder не существует, то ничего не происходит, и выполняется следующий блок. 2) Если оно существует, но выдается исключение, то оно устарело и не должно использоваться в любом случае, поэтому оно продолжается в следующем блоке try. В идеале вы должны проверить, поддерживается ли Blob первым, и даже перед этой проверкой поддержки toBlob
TaylorMac

5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

может быть использован без try catch.

Спасибо check_ca. Отличная работа.


1
Это все равно выдаст ошибку, если браузер поддерживает устаревший BlobBuilder. Браузер будет использовать старый метод, если он его поддерживает, даже если он поддерживает новый метод. Это нежелательно, см. Подход Криса Боско ниже
TaylorMac

4

Исходный ответ от Stoive легко исправить, изменив последнюю строку для размещения Blob:

function dataURItoBlob (dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);
    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    return new Blob([ab],{type: mimeString});
}

3

Вот ES6-версия ответа Стоива :

export class ImageDataConverter {
  constructor(dataURI) {
    this.dataURI = dataURI;
  }

  getByteString() {
    let byteString;
    if (this.dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(this.dataURI.split(',')[1]);
    } else {
      byteString = decodeURI(this.dataURI.split(',')[1]);
    }
    return byteString;
  }

  getMimeString() {
    return this.dataURI.split(',')[0].split(':')[1].split(';')[0];
  }

  convertToTypedArray() {
    let byteString = this.getByteString();
    let ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return ia;
  }

  dataURItoBlob() {
    let mimeString = this.getMimeString();
    let intArray = this.convertToTypedArray();
    return new Blob([intArray], {type: mimeString});
  }
}

Использование:

const dataURL = canvas.toDataURL('image/jpeg', 0.5);
const blob = new ImageDataConverter(dataURL).dataURItoBlob();
let fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);

3

Спасибо! @steovi для этого решения.

Я добавил поддержку в версию ES6 и изменил с unescape на dataURI (unescape устарела).

converterDataURItoBlob(dataURI) {
    let byteString;
    let mimeString;
    let ia;

    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(dataURI.split(',')[1]);
    } else {
      byteString = encodeURI(dataURI.split(',')[1]);
    }
    // separate out the mime component
    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {type:mimeString});
}

1

сделай это просто: D

function dataURItoBlob(dataURI,mime) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs

    var byteString = window.atob(dataURI);

    // separate out the mime component


    // write the bytes of the string to an ArrayBuffer
    //var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    var blob = new Blob([ia], { type: mime });

    return blob;
}

-2

toDataURL дает вам строку, и вы можете поместить эту строку в скрытый ввод.


Не могли бы вы привести пример? Я не хочу загружать строку base64 (что делает то, что делает <input type=hidden value="data:..." />), я хочу загрузить данные файла (как и то <input type="file" />, что делает, за исключением того, что вы не можете устанавливать valueсвойства для них).
Stoive

Это должен быть комментарий, а не ответ. Пожалуйста, уточните ответ с надлежащим объяснением. @Cat Chen
Lucky

-5

У меня была точно такая же проблема, как у Равиндера Паяла, и я нашел ответ. Попробуй это:

var dataURL = canvas.toDataURL("image/jpeg");

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});

2
Вы действительно предлагаете использовать Parse.com? Вы должны отметить, что ваш ответ требует зависимости!
Пьер Мауи

2
WTF? Почему кто-либо будет загружать код base64 изображения на сервер PARSE, а затем загружать? Когда мы можем напрямую загрузить base64 на наши серверы, главное, чтобы для загрузки строки base64 или файла изображения потребовались одни и те же данные. И если вы просто хотите, чтобы пользователь мог загрузить изображение, вы можете использовать этоwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.