Konvertieren Sie den Daten-URI in eine Datei und hängen Sie ihn an FormData an

282

Ich habe versucht, einen HTML5-Bild-Uploader wie den auf der Mozilla Hacks- Website erneut zu implementieren , aber das funktioniert mit WebKit-Browsern. Ein Teil der Aufgabe besteht darin, eine Bilddatei aus dem canvasObjekt zu extrahieren und zum Hochladen an ein FormData- Objekt anzuhängen .

Das Problem ist, dass das FormData-Objekt zwar canvasdie toDataURLFunktion hat, eine Darstellung der Bilddatei zurückzugeben, jedoch nur Datei- oder Blob-Objekte von der Datei-API akzeptiert .

Die Mozilla-Lösung verwendete die folgende Nur-Firefox-Funktion für canvas:

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

... was in WebKit-Browsern nicht verfügbar ist. Die beste Lösung, die ich mir vorstellen kann, besteht darin, eine Möglichkeit zu finden, einen Daten-URI in ein Dateiobjekt zu konvertieren, von dem ich dachte, dass es Teil der Datei-API ist, aber ich kann für mein ganzes Leben nichts finden, um dies zu tun.

Ist es möglich? Wenn nicht, Alternativen?

Vielen Dank.

Stoive
quelle
Wenn Sie die DataURI eines Bildes auf dem Server speichern
möchten

Antworten:

469

Nachdem ich mit ein paar Dingen herumgespielt hatte, gelang es mir, dies selbst herauszufinden.

Zunächst wird eine dataURI in einen Blob konvertiert:

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

Von dort aus ist es einfach, die Daten an ein Formular anzuhängen, sodass sie als Datei hochgeladen werden:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
quelle
28
Warum passiert das immer ... Sie versuchen stundenlang, ein Problem mit SO-Suchen hier und da zu lösen. Dann postest du eine Frage. Innerhalb einer Stunde erhalten Sie die Antwort auf eine andere Frage. Nicht, dass ich mich beschwere ... stackoverflow.com/questions/9388412/…
syaz
@stoive Ich kann Blob konstruieren, aber können Sie bitte erklären, wie Sie das POSToder PUTzu S3 konstruieren ?
Gaurav Shah
1
@mimo - Zeigt auf den Basiswert ArrayBuffer, der dann in die BlobBuilderInstanz geschrieben wird.
Stoive
2
@stoive In diesem Fall, warum ist es nicht bb.append (ia)?
Mimo
4
Vielen Dank! Dies löste mein Problem mit einer kleinen Korrekturvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara
141

BlobBuilder und ArrayBuffer sind jetzt veraltet. Hier ist der Code des obersten Kommentars, der mit dem Blob-Konstruktor aktualisiert wurde:

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'});
}
vava720
quelle
2
Nur eine Idee: array=[]; array.length=binary.length;... array[i]=bina... usw. Das Array ist also vorab zugewiesen. Es erspart einen Push (), der das Array bei jeder Iteration erweitern muss, und wir verarbeiten hier möglicherweise Millionen von Elementen (= Bytes), also ist es wichtig.
DDS
2
Scheitert auch für mich auf Safari. @ WilliamT. Die Antwort funktioniert jedoch für Firefox / Safari / Chrome.
ObscureRobot
"binär" ist ein leicht irreführender Name, da es sich nicht um ein Array von Bits handelt, sondern um ein Array von Bytes.
Niels Abildgaard
1
"type: 'image / jpeg'" - was ist, wenn es sich um ein PNG-Bild handelt ODER wenn Sie die Bilderweiterung nicht im Voraus kennen?
Jasper
Das Erstellen von Uint8Array ist zunächst besser als das Erstellen eines Arrays und das anschließende Konvertieren in Uint8Array.
Cuixiping
52

Dieser funktioniert in iOS und Safari.

Sie müssen die ArrayBuffer-Lösung von Stoive verwenden, können jedoch nicht BlobBuilder verwenden, wie vava720 angibt. Hier ist das Mashup von beiden.

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' });
}
William T.
quelle
11
Großartig! Aber Sie könnten die MIME-Zeichenfolge trotzdem dynamisch halten, wie in Stoives Lösung, nehme ich an? // trenne die MIME-Komponente var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Per Quested Aronsson
Was ist mit dem Fallback für iOS6 mit Webkit-Präfix? Wie gehst du damit um?
Confile
30

Firefox verfügt über die Methoden canvas.toBlob () und canvas.mozGetAsFile () .

Andere Browser jedoch nicht.

Wir können Dataurl von Canvas abrufen und dann Dataurl in Blob-Objekt konvertieren.

Hier ist meine dataURLtoBlob()Funktion. Es ist sehr kurz.

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

Verwenden Sie diese Funktion mit FormData, um Ihre Zeichenfläche oder Daten-URL zu verarbeiten.

Beispielsweise:

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

Sie können auch eine HTMLCanvasElement.prototype.toBlobMethode für einen Browser ohne Gecko-Engine erstellen .

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

Funktioniert jetzt canvas.toBlob()für alle modernen Browser, nicht nur für Firefox. Zum Beispiel:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
cuixiping
quelle
1
Die hier erwähnte Polyfüllung für canvas.toBlob ist meiner Meinung nach der richtige Weg, um dieses Problem zu lösen.
Jakob Kruse
2
Ich möchte das Letzte in diesem Beitrag hervorheben: "Jetzt funktioniert canvas.toBlob () für alle modernen Browser."
Eric Simonton
25

Mein bevorzugter Weg ist canvas.toBlob ()

Aber hier ist noch eine andere Möglichkeit, base64 mit fetch ^^ in einen Blob umzuwandeln.

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

Endlos
quelle
Was ist Fetch und wie ist es relevant?
Ricardo Freitas
Fetch ist eine moderne Ajax-Methode, die Sie anstelle von verwenden können, XMLHttpRequestda Daten-URL nur eine URL ist. Sie können Ajax verwenden, um diese Ressource abzurufen, und Sie haben die Möglichkeit, selbst zu entscheiden, ob Sie sie als Blob, Array-Puffer oder Text verwenden möchten
Endless
1
@Endless 'fetch ()' ein lokaler base64 String ... ein wirklich cleverer Hack!
Diego ZoracKy
1
Beachten Sie, dass blob:und data:nicht universell von allen holen Implementierungen unterstützt. Wir verwenden diesen Ansatz, da wir wissen, dass wir uns nur mit mobilen Browsern (WebKit) befassen werden, Edge jedoch keine Unterstützung bietet: developer.mozilla.org/en-US/docs/Web/API/…
oligofren
19

Dank @Stoive und @ vava720 habe ich beide auf diese Weise kombiniert, ohne den veralteten BlobBuilder und ArrayBuffer zu verwenden

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});
}
Mimo
quelle
12

Der sich entwickelnde Standard scheint canvas.toBlob () zu sein, nicht canvas.getAsFile (), wie Mozilla zu erraten riskierte.

Ich sehe noch keinen Browser, der dies unterstützt :(

Danke für diesen tollen Thread!

Außerdem sollte jeder, der versucht, die akzeptierte Antwort zu erhalten, mit BlobBuilder vorsichtig sein, da ich finde, dass die Unterstützung begrenzt ist (und einen Namespace aufweist):

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

Haben Sie die Polyfüllung einer anderen Bibliothek für BlobBuilder verwendet?

Chris Bosco
quelle
Ich habe Chrome ohne Polyfills verwendet und kann mich nicht erinnern, auf Namespaces gestoßen zu sein. Ich bin gespannt canvas.toBlob()- es scheint viel angemessener als getAsFile.
Stoive
1
BlobBuilderscheinen zugunsten vonBlob
Sandstrom
BlobBuilder ist veraltet und dieses Muster ist schrecklich. Besser wäre: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); Die verschachtelten Try-Catches sind wirklich hässlich und was passiert, wenn keiner der Builder verfügbar ist?
Chris Hanson
Wie ist das schrecklich? Wenn eine Ausnahme ausgelöst wird und 1) BlobBuilder nicht existiert, passiert nichts und der nächste Block wird ausgeführt. 2) Wenn es existiert, aber eine Ausnahme ausgelöst wird, ist es veraltet und sollte sowieso nicht verwendet werden, sodass es mit dem nächsten try-Block fortfährt. Idealerweise würden Sie zuerst prüfen, ob Blob unterstützt wird, und noch vor dieser Überprüfung auf toBlob-Unterstützung
TaylorMac
5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

kann ohne den try catch verwendet werden.

Vielen Dank an check_ca. Gute Arbeit.

Nafis Ahmad
quelle
1
Dies wird immer noch einen Fehler auslösen, wenn der Browser den veralteten BlobBuilder unterstützt. Der Browser verwendet die alte Methode, wenn er sie unterstützt, auch wenn er die neue Methode unterstützt. Dies ist nicht erwünscht, siehe Chris Boscos Ansatz unten
TaylorMac
4

Die ursprüngliche Antwort von Stoive kann leicht behoben werden, indem die letzte Zeile geändert wird, um Blob aufzunehmen:

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});
}
Topkara
quelle
3

Hier ist eine ES6-Version von Stoives Antwort :

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

Verwendungszweck:

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);
vekerdyb
quelle
3

Vielen Dank! @steovi für diese Lösung.

Ich habe die ES6-Version unterstützt und von unescape zu dataURI geändert (unescape ist veraltet).

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});
}
wilfredonoyola
quelle
1

mach es einfach: 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;
}
Sendy
quelle
-2

toDataURL gibt Ihnen eine Zeichenfolge und Sie können diese Zeichenfolge in eine versteckte Eingabe einfügen.

Katze Chen
quelle
Könnten Sie bitte ein Beispiel geben? Ich möchte keine base64-Zeichenfolge <input type=hidden value="data:..." />hochladen (was auch der <input type="file" />Fall ist ), sondern die Dateidaten hochladen (wie dies der Fall ist, außer dass Sie die valueEigenschaft für diese nicht festlegen dürfen ).
Stoive
Dies sollte eher ein Kommentar als eine Antwort sein. Bitte erläutern Sie die Antwort mit der richtigen Erklärung. @ Cat Chen
Lucky
-5

Ich hatte genau das gleiche Problem wie Ravinder Payal und habe die Antwort gefunden. Versuche dies:

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

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
Feng
quelle
2
Schlagen Sie wirklich vor, Parse.com zu verwenden? Sie sollten erwähnen, dass Ihre Antwort Abhängigkeiten erfordert!
Pierre Maoui
2
WTF? Warum lädt jemand den base64-Bildcode auf den PARSE-Server hoch und lädt ihn dann herunter? Wenn wir base64 direkt auf unsere Server hochladen können, ist es wichtig, dass zum Hochladen der base64-Zeichenfolge oder der Bilddatei dieselben Daten erforderlich sind. Und wenn Sie dem Benutzer nur erlauben möchten, das Bild herunterzuladen, können Sie dies verwendenwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal