แปลง Data URI เป็น File จากนั้นผนวกไปที่ FormData


283

ฉันพยายามที่จะใช้งานเครื่องมืออัปโหลดรูปภาพ HTML5 ใหม่เช่นเดียวกับบนเว็บไซต์Mozilla Hacksแต่ทำงานได้กับเบราว์เซอร์ WebKit ส่วนหนึ่งของงานคือการแยกไฟล์ภาพจากcanvasวัตถุและผนวกเข้ากับวัตถุFormDataสำหรับการอัปโหลด

ปัญหาก็คือว่าในขณะที่canvasมีtoDataURLฟังก์ชั่นที่จะกลับมาเป็นตัวแทนของไฟล์ภาพวัตถุ FormData ยอมรับเฉพาะไฟล์หรือวัตถุหยดจากไฟล์ API

โซลูชัน Mozilla ใช้ฟังก์ชัน Firefox เท่านั้นต่อไปนี้ในcanvas:

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

... ซึ่งไม่สามารถใช้ได้กับเบราว์เซอร์ WebKit ทางออกที่ดีที่สุดที่ฉันคิดคือหาวิธีแปลง Data URI เป็นวัตถุไฟล์ซึ่งฉันคิดว่าอาจเป็นส่วนหนึ่งของ File API แต่ฉันไม่สามารถหาสิ่งที่จะทำให้ชีวิตของฉันทำได้

เป็นไปได้ไหม? ถ้าไม่ทางเลือกใด ๆ

ขอบคุณ


หากคุณต้องการบันทึก DataURI ของภาพในเซิร์ฟเวอร์: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

คำตอบ:


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 ได้อย่างไร
Gaurav Shah เมื่อ

1
@mimo - มันชี้ไปที่ต้นแบบArrayBufferซึ่งถูกเขียนไปยังBlobBuilderอินสแตนซ์แล้ว
Stoive

2
@stoive ในกรณีนี้เหตุใดจึงไม่ใช่ bb.append (ia)
Mimo

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... ฯลฯ ดังนั้นอาร์เรย์จึงได้รับการจัดสรรล่วงหน้า มันช่วยประหยัดการกด () ที่ต้องขยายอาร์เรย์แต่ละการวนซ้ำและเรากำลังประมวลผลรายการนับล้านรายการ (= ไบต์) ที่นี่ดังนั้นจึงเป็นเรื่องสำคัญ
DDS

2
ยังล้มเหลวสำหรับฉันใน Safari @WilliamT คำตอบของมันใช้ได้กับ Firefox / Safari / Chrome
ObscureRobot

"binary" เป็นชื่อที่ทำให้เข้าใจผิดเล็กน้อยเนื่องจากไม่ใช่อาร์เรย์ของบิต แต่เป็นอาร์เรย์ของไบต์
Niels Abildgaard

1
"type: 'image / jpeg'" - จะเป็นอย่างไรถ้าเป็นรูปภาพ png หรือหากคุณไม่รู้จักนามสกุลรูปภาพล่วงหน้า?
แจสเปอร์

สร้าง Uint8Array ในตอนแรกดีกว่าสร้าง Array แล้วแปลงเป็น 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 ของสตริงได้เช่นใน Stoive's solution ฉันคิดว่า? // แยกองค์ประกอบ mime ออก var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
ต่อภารกิจ Aronsson

อะไรคือทางเลือกสำหรับ iOS6 ที่มีคำนำหน้า webkit? คุณจัดการกับสิ่งนี้ได้อย่างไร
confile

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 ที่กล่าวถึงที่นี่เป็นวิธีที่ถูกต้องในการจัดการปัญหานี้ IMHO
Jakob Kruse

2
ฉันอยากจะเน้นสิ่งสุดท้ายในโพสต์นี้: "ตอนนี้ canvas.toBlob () ใช้ได้กับเบราว์เซอร์สมัยใหม่ทั้งหมด"
Eric Simonton

25

วิธีที่ฉันชอบคือcanvas.toBlob ()

แต่นี่ก็เป็นอีกวิธีในการแปลง base64 ไปเป็น blob โดยใช้การดึงข้อมูล ^^,

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


การดึงข้อมูลคืออะไรและเกี่ยวข้องอย่างไร
Ricardo Freitas

ดึงข้อมูลเป็นวิธีการที่อาแจ็กซ์ที่ทันสมัยที่คุณสามารถใช้แทนXMLHttpRequestตั้งแต่ URL ข้อมูลเป็นเพียง URL, คุณสามารถใช้อาแจ็กซ์สามารถดึงข้อมูลทรัพยากรและคุณมีตัวเองเป็นตัวเลือกในการตัดสินใจว่าคุณต้องการให้มันเป็นหยด arraybuffer หรือข้อความ
ไม่มีที่สิ้นสุด

1
@ Endless 'fetch ()' สตริง base64 ในเครื่อง ... แฮ็คที่ฉลาดจริงๆ!
Diego ZoracKy

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ดูเหมือนมากขึ้นเหมาะสมกว่า
Stoive

1
BlobBuilderดูเหมือนจะเลิกในความโปรดปรานของBlob
sandstrom

BlobBuilder เลิกใช้แล้วและรูปแบบนี้แย่มาก จะดีกว่า: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); การพยายามจับแบบซ้อนน่าเกลียดจริง ๆ แล้วจะเกิดอะไรขึ้นหากไม่มีผู้สร้างอยู่?
Chris Hanson

มันช่างน่ากลัวขนาดนี้เหรอ? หากมีการโยนข้อยกเว้นและ 1) BlobBuilder ไม่มีอยู่จะไม่มีสิ่งใดเกิดขึ้นและจะมีการดำเนินการบล็อกถัดไป 2) หากมีอยู่ แต่มีข้อผิดพลาดเกิดขึ้นแสดงว่าเลิกใช้แล้วและไม่ควรใช้ต่อไปดังนั้นจึงยังคงอยู่ในบล็อกการทดลองถัดไป เป็นการดีที่คุณจะตรวจสอบว่าได้รับการสนับสนุน Blob เป็นครั้งแรกและแม้กระทั่งก่อนที่การตรวจสอบนี้เพื่อสนับสนุน Blob
TaylorMac

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

สามารถใช้โดยไม่ต้องลอง catch

ขอบคุณที่ check_ca การทำงานที่ดี.


1
สิ่งนี้จะยังคงมีข้อผิดพลาดหากเบราว์เซอร์รองรับ BlobBuilder ที่เลิกใช้แล้ว เบราว์เซอร์จะใช้วิธีเก่าหากรองรับแม้จะรองรับวิธีใหม่ สิ่งนี้ไม่เป็นที่ต้องการโปรดดูแนวทางของ Chris Bosco ด้านล่าง
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คุณสมบัติเหล่านี้)
Stoive

นี่ควรเป็นความคิดเห็นมากกว่าคำตอบ โปรดอธิบายคำตอบอย่างละเอียดพร้อมคำอธิบายที่เหมาะสม @Cat Chen
Lucky

-5

ฉันมีปัญหาเดียวกันกับ Ravinder Payal และฉันพบคำตอบแล้ว ลองสิ่งนี้:

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

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

2
คุณแนะนำให้ใช้ Parse.com จริงๆหรือ? คุณควรพูดถึงว่าคำตอบของคุณต้องการการพึ่งพา!
Pierre Maoui

2
WTF หรือไม่ ทำไมทุกคนจะอัปโหลดรหัสภาพ base64 ไปยังเซิร์ฟเวอร์ PARSE แล้วดาวน์โหลดเมื่อเราสามารถอัปโหลด base64 โดยตรงบนเซิร์ฟเวอร์ของเราและที่สำคัญคือต้องใช้ข้อมูลเดียวกันในการอัปโหลดสตริงหรือไฟล์รูปภาพ base64 และถ้าคุณเพียงต้องการให้ผู้ใช้ดาวน์โหลดภาพคุณสามารถใช้มันได้window.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.