Перетворити 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, який, на мою думку, міг би бути частиною API файлу, але я не можу за все життя знайти щось для цього.

Це можливо? Якщо ні, то альтернативи?

Дякую.


Якщо ви хочете зберегти DataURI образу в сервері: stackoverflow.com/a/50131281/5466401
Sibin Джон Mattappallil

Відповіді:


471

Погравши з кількома речами, мені вдалося це зрозуміти сам.

Перш за все, це перетворить даніURI в 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
Чому це завжди буває ... Ви намагаєтеся вирішити проблему годинами та годинами, шукаючи тут і там. Потім ви ставите запитання. Протягом години ви отримуєте відповідь з іншого питання. Не те , щоб я скаржуся ... stackoverflow.com/questions/9388412 / ...
syaz

@stoive Я можу скласти Blob, але чи можете ви пояснити, як ви будуєте POSTабо PUTдо S3?
Гаурав Шах

1
@mimo - Це вказує на основу ArrayBuffer, яку потім записують до BlobBuilderекземпляра.
Stoive

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.

Вам потрібно використовувати рішення AriveBuffer 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
Чудово! Але ви все-таки зможете тримати мімічну струну динамічною, як, наприклад, у рішенні Stoive? // відокремте компонент mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
за запитом Aronsson,

Що з резервної копії для iOS6 з префіксом webkit? Як ви справляєтеся з цим?
confile

30

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

Але інші браузери цього не роблять.

Ми можемо отримати dataurl з полотна, а потім перетворити 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метод для браузера не gecko.

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
Поліфайл для canvas.toBlob, згаданий тут, є правильним способом вирішення цього питання IMHO.
Якоб Крузе

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 - це сучасний метод 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) Якщо він існує, але викид викинуто, він застарілий і не повинен використовуватись, тому він продовжується в наступний блок спроб. В ідеалі ви повинні перевірити , якщо Blob підтримується першим, і навіть до цієї перевірки для підтримки toBlob
TaylorMac

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

можна використовувати без спроб улов.

Дякуємо за 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

Ось версія відповіді Stoive 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властивість на них).
Стоїть

Це повинен бути коментар, а не відповідь. Будь ласка, детально поясніть відповідь. @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 на наші сервери, і головне, що для завантаження базового рядка або файлу зображення потрібні ті самі дані. І якщо ви просто хочете дозволити користувачу завантажити зображення, ви можете скористатися цимwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.