Converti l'URI dei dati in file, quindi aggiungi a FormData


283

Ho cercato di implementare nuovamente un uploader di immagini HTML5 come quello sul sito Mozilla Hacks , ma funziona con i browser WebKit. Parte dell'attività consiste nell'estrarre un file di immagine canvasdall'oggetto e aggiungerlo a un oggetto FormData per il caricamento.

Il problema è che mentre canvasha la toDataURLfunzione di restituire una rappresentazione del file di immagine, l'oggetto FormData accetta solo oggetti File o BLOB dall'API File .

La soluzione Mozilla ha utilizzato la seguente funzione solo per Firefox su canvas:

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

... che non è disponibile sui browser WebKit. La soluzione migliore che mi viene in mente è trovare un modo per convertire un URI di dati in un oggetto File, che pensavo potesse far parte dell'API File, ma non riesco a trovare qualcosa per farlo per tutta la vita.

È possibile? In caso contrario, qualche alternativa?

Grazie.


Se si desidera salvare il DataURI di un'immagine in server: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Risposte:


471

Dopo aver giocato con alcune cose, sono riuscito a capirlo da solo.

Innanzitutto, questo convertirà un dataURI in un 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});
}

Da lì, aggiungere i dati a un modulo in modo che vengano caricati come file è facile:

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

29
Perché succede sempre ... Cerchi di risolvere un problema per ore e ore con ricerche SO qua e là. Quindi pubblichi una domanda. Entro un'ora ricevi la risposta da un'altra domanda. Non che mi lamenti ... stackoverflow.com/questions/9388412/...
syaz

@stoive Sono in grado di contrattare Blob ma puoi spiegarmi come si costruisce POSTo PUTsu S3?
Gaurav Shah,

1
@mimo - Indica il sottostante ArrayBuffer, che viene quindi scritto BlobBuildernell'istanza.
Stoive il

2
@stoive In quel caso, perché non è bb.append (ia)?
Mimo,

4
Grazie! Ciò ha risolto il mio problema con una piccola correzionevar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara,

141

BlobBuilder e ArrayBuffer sono ora obsoleti, ecco il codice del commento principale aggiornato con il costruttore 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
Solo un'idea: array=[]; array.length=binary.length;... array[i]=bina... ecc. Quindi l'array è pre-allocato. Salva un push () dovendo estendere l'array ogni iterazione, e qui stiamo elaborando eventualmente milioni di elementi (= byte), quindi è importante.
DDS

2
Anche per me non funziona su Safari. @WilliamT. La risposta funziona anche per Firefox / Safari / Chrome.
ObscureRobot

"binario" è un nome leggermente fuorviante, in quanto non è un array di bit, ma un array di byte.
Niels Abildgaard,

1
"type: 'image / jpeg'" - cosa succede se si tratta di un'immagine png O se non si conosce in anticipo l'estensione dell'immagine?
Jasper,

Creare Uint8Array all'inizio è meglio che creare un array e poi convertirlo in Uint8Array.
cuixiping

52

Questo funziona su iOS e Safari.

Devi usare la soluzione ArrayBuffer di Stoive ma non puoi usare BlobBuilder, come indica vava720, quindi ecco il mashup di entrambi.

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
Grande! Ma potresti ancora mantenere dinamica la stringa di mime, come nella soluzione di Stoive, suppongo? // separa il componente mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Per Quested Aronsson

Cos'è il fallback per iOS6 con prefisso webkit? come fai a gestirlo?
Confile il

30

Firefox ha metodi canvas.toBlob () e canvas.mozGetAsFile () .

Ma altri browser no.

Possiamo ottenere dataurl da canvas e quindi convertire dataurl in oggetto BLOB.

Ecco la mia dataURLtoBlob()funzione È molto corto

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

Utilizzare questa funzione con FormData per gestire il canvas o il dataurl.

Per esempio:

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

Inoltre, è possibile creare un HTMLCanvasElement.prototype.toBlobmetodo per il browser del motore non geco.

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

Ora canvas.toBlob()funziona per tutti i browser moderni, non solo per Firefox. Per esempio:

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

1
Il polyfill per canvas.toBlob menzionato qui è il modo corretto di gestire questo problema IMHO.
Jakob Kruse,

2
Vorrei sottolineare l'ultima cosa in questo post: "Ora canvas.toBlob () funziona per tutti i browser moderni".
Eric Simonton,

25

Il mio modo preferito è canvas.toBlob ()

Ma comunque qui c'è ancora un altro modo per convertire base64 in un BLOB usando 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})
})


che cos'è il recupero e in che modo è rilevante?
Ricardo Freitas,

Il recupero è un moderno metodo ajax che puoi usare invece che XMLHttpRequestpoiché l'URL dei dati è solo un url, puoi usare ajax per recuperare quella risorsa e hai un'opzione per decidere se lo vuoi come BLOB, arraybuffer o testo
Endless

1
@Endless 'fetch ()' una stringa base64 locale ... un trucco davvero intelligente!
Diego ZoracKy

1
Tieni presente che blob:e data:non sono universalmente supportati da tutte le implementazioni di recupero. Usiamo questo approccio, poiché sappiamo che ci occuperemo solo dei browser mobili (WebKit), ma Edge ad esempio non lo supporta: developer.mozilla.org/en-US/docs/Web/API/…
oligofren

19

Grazie a @Stoive e @ vava720 ho combinato i due in questo modo, evitando di utilizzare BlobBuilder e ArrayBuffer obsoleti

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

Lo standard in evoluzione sembra essere canvas.toBlob () e non canvas.getAsFile () come Mozilla ha rischiato di indovinare.

Non vedo ancora alcun browser che lo supporti :(

Grazie per questo fantastico thread!

Inoltre, chiunque cerchi la risposta accettata dovrebbe stare attento con BlobBuilder poiché sto trovando che il supporto sia limitato (e spaziato dal nome):

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

Stavi usando il polyfill di un'altra libreria per BlobBuilder?


Stavo usando Chrome senza polyfill e non ricordo di essermi imbattuto in namespace. Prevedo con impazienza canvas.toBlob()- sembra molto più appropriato di getAsFile.
Stoive,

1
BlobBuildersembra essere deprecato a favore diBlob
sandstrom il

BlobBuilder è obsoleto e questo modello è terribile. Meglio sarebbe: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); le catture di prova nidificate sono davvero brutte e cosa succede se nessuno dei costruttori è disponibile?
Chris Hanson,

Come è terribile? Se viene generata un'eccezione e 1) BlobBuilder non esiste, non accade nulla e viene eseguito il blocco successivo. 2) Se esiste, ma viene generata un'eccezione, viene deprecata e non deve essere utilizzata comunque, quindi continua nel blocco di prova successivo. Idealmente, verifichi se Blob è supportato per primo e anche prima di questo controllo per il supporto toBlob
TaylorMac

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

può essere utilizzato senza il tentativo di cattura.

Grazie a check_ca. Ottimo lavoro.


1
Ciò genererà comunque un errore se il browser supporta BlobBuilder obsoleto. Il browser utilizzerà il vecchio metodo se lo supporta, anche se supporta il nuovo metodo. Questo non è desiderato, vedi l'approccio di Chris Bosco di seguito
TaylorMac,

4

La risposta originale di Stoive è facilmente risolvibile modificando l'ultima riga per adattarsi a 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

Ecco una versione ES6 della risposta di Stoive :

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

Uso:

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

Grazie! @steovi per questa soluzione.

Ho aggiunto il supporto alla versione ES6 e sono passato da unescape a dataURI (unescape è obsoleto).

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

rendilo semplice: 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 ti dà una stringa e puoi mettere quella stringa in un input nascosto.


Potresti fare un esempio, per favore? Non voglio caricare una stringa base64 (che è ciò che <input type=hidden value="data:..." />farebbe), voglio caricare i dati del file (come quello che <input type="file" />fa, tranne che non ti è permesso impostare la valueproprietà su questi).
Stoive

Questo dovrebbe essere un commento piuttosto che una risposta. Si prega di elaborare la risposta con una spiegazione adeguata. @Cat Chen
Lucky

-5

Ho avuto esattamente lo stesso problema di Ravinder Payal e ho trovato la risposta. Prova questo:

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

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

2
Stai davvero suggerendo di usare Parse.com? Dovresti dire che la tua risposta richiede dipendenze!
Pierre Maoui,

2
WTF? Perché qualcuno caricherà il codice immagine base64 sul server PARSE e poi lo scaricherà? Quando possiamo caricare direttamente base64 sui nostri server e la cosa principale è che ci vogliono gli stessi dati per caricare la stringa base64 o il file immagine. E se vuoi solo consentire all'utente di scaricare l'immagine, puoi usare questowindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.