Chuyển đổi URI dữ liệu thành tệp sau đó nối vào FormData


282

Tôi đã cố gắng triển khai lại trình tải lên hình ảnh HTML5 giống như trên trang web Mozilla Hacks , nhưng nó hoạt động với các trình duyệt WebKit. Một phần của nhiệm vụ là trích xuất một tệp hình ảnh từ canvasđối tượng và nối nó vào đối tượng FormData để tải lên.

Vấn đề là trong khi canvastoDataURLchức năng trả về một đại diện của tệp hình ảnh, đối tượng FormData chỉ chấp nhận các đối tượng Tệp hoặc Blob từ API tệp .

Giải pháp Mozilla đã sử dụng chức năng chỉ dành cho Firefox sau đây trên canvas:

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

... không khả dụng trên các trình duyệt WebKit. Giải pháp tốt nhất tôi có thể nghĩ đến là tìm cách chuyển đổi URI dữ liệu thành đối tượng Tệp, mà tôi nghĩ có thể là một phần của API tệp, nhưng tôi không thể tìm thấy điều gì đó để làm điều đó.

Có thể không? Nếu không, có sự thay thế nào không?

Cảm ơn.


Nếu bạn muốn lưu DataURI của một hình ảnh trong máy chủ: stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Câu trả lời:


470

Sau khi chơi xung quanh với một vài thứ, tôi đã tự mình tìm ra điều này.

Trước hết, điều này sẽ chuyển đổi dataURI thành 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});
}

Từ đó, việc thêm dữ liệu vào một biểu mẫu sao cho nó sẽ được tải lên dưới dạng tệp rất dễ dàng:

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

28
Tại sao điều này luôn xảy ra ... Bạn cố gắng giải quyết vấn đề hàng giờ liền với các tìm kiếm SO ở đây và đó. Sau đó, bạn gửi một câu hỏi. Trong vòng một giờ bạn nhận được câu trả lời từ một câu hỏi khác. Không phải là tôi đang phàn nàn ... stackoverflow.com/questions/9388412/ từ
syaz

@stoive Tôi có thể tạo ra Blob nhưng bạn có thể giải thích cách bạn xây dựng POSThoặc PUTcho S3 không?
Gaurav Shah

1
@mimo - Nó trỏ đến bên dưới ArrayBuffer, sau đó được ghi vào BlobBuilderthể hiện.
Stoive

2
@stoive Trong trường hợp đó tại sao không phải là bb.append (ia)?
Mimo

4
Cảm ơn! Điều này đã giải quyết vấn đề của tôi với một sự điều chỉnh nhỏvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara

141

BlobBuilder và ArrayBuffer hiện không được chấp nhận, đây là mã nhận xét hàng đầu được cập nhật với hàm tạo 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
Chỉ là một ý tưởng: array=[]; array.length=binary.length;... array[i]=bina... vv Vì vậy, mảng được phân bổ trước. Nó tiết kiệm một lần đẩy () phải mở rộng mảng mỗi lần lặp và chúng tôi đang xử lý hàng triệu mục (= byte) ở đây, vì vậy nó rất quan trọng.
DDS

2
Cũng thất bại với tôi trên Safari. @WilliamT. Tuy nhiên, câu trả lời hoạt động cho Firefox / Safari / Chrome.
ObscureRobot

"binary" là một tên hơi sai lệch, vì nó không phải là một mảng bit, mà là một mảng byte.
Niels Abildgaard

1
"type: 'image / jpeg'" - nếu đó là hình ảnh png HOẶC nếu bạn không biết phần mở rộng hình ảnh trước thì sao?
Jasper

Tạo Uint8Array lúc đầu tốt hơn là tạo một Array và sau đó chuyển đổi thành Uint8Array.
cuixiping

52

Cái này hoạt động trong iOS và Safari.

Bạn cần sử dụng giải pháp ArrayBuffer của Stoive nhưng bạn không thể sử dụng BlobBuilder, như vava720 chỉ ra, vì vậy đây là bản mashup của cả hai.

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
Tuyệt quá! Nhưng bạn vẫn có thể giữ cho chuỗi mime động, như trong giải pháp của Stoive, tôi cho rằng? // tách thành phần mime var mimeString = dataURI.split (',') [0] .split (':') [1] .split (';') [0]
Per Quested Aronsson

Điều gì với dự phòng cho iOS6 với tiền tố webkit? Làm thế nào bạn có thể xoay xở được chuyện này?
hạn

30

Firefox có các phương thức canvas.toBlob ()canvas.mozGetAsFile () .

Nhưng các trình duyệt khác thì không.

Chúng ta có thể lấy dataurl từ canvas và sau đó chuyển đổi dataurl sang đối tượng blob.

Đây là dataURLtoBlob()chức năng của tôi . Nó rất ngắn.

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

Sử dụng chức năng này với FormData để xử lý canvas hoặc dataurl của bạn.

Ví dụ:

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

Ngoài ra, bạn có thể tạo một HTMLCanvasElement.prototype.toBlobphương thức cho trình duyệt động cơ không tắc kè.

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

Bây giờ canvas.toBlob()hoạt động cho tất cả các trình duyệt hiện đại không chỉ Firefox. Ví dụ:

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

1
Polyfill cho canvas.toBlob được đề cập ở đây là cách chính xác để xử lý vấn đề này IMHO.
Jakob Kruse

2
Tôi muốn nhấn mạnh điều cuối cùng trong bài viết này: "Bây giờ canvas.toBlob () hoạt động cho tất cả các trình duyệt hiện đại."
Eric Simonton

25

Cách ưa thích của tôi là canvas.toBlob ()

Nhưng dù sao ở đây vẫn là một cách khác để chuyển đổi Base64 thành blob bằng cách sử dụng 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 là gì và nó có liên quan như thế nào?
Ricardo Freitas

Fetch là một phương thức ajax hiện đại mà bạn có thể sử dụng thay vì XMLHttpRequesturl dữ liệu chỉ là một url, Bạn có thể sử dụng ajax để tìm nạp tài nguyên đó và bạn có cho mình một tùy chọn để quyết định xem bạn muốn nó như blob, Arraybuffer hay văn bản
Endless

1
@Endless 'fetch ()' một chuỗi base64 cục bộ ... một cách hack thực sự thông minh!
Diego ZoracKy

1
Hãy ghi nhớ rằng blob:data:không được hỗ trợ phổ biến bởi tất cả các triển khai tìm nạp. Chúng tôi sử dụng phương pháp này, vì chúng tôi biết rằng chúng tôi sẽ chỉ giao dịch với các trình duyệt di động (WebKit), nhưng chẳng hạn, Edge không hỗ trợ itt: developer.mozilla.org/en-US/docs/Web/API/
trộm

19

Nhờ @Stoive và @ vava720, tôi đã kết hợp cả hai theo cách này, tránh sử dụng BlobBuilder và ArrayBuffer không dùng nữa

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

Tiêu chuẩn đang phát triển có vẻ là canvas.toBlob () chứ không phải canvas.getAsFile () như Mozilla nguy hiểm để đoán.

Tôi chưa thấy trình duyệt nào hỗ trợ nó :(

Cảm ơn cho chủ đề tuyệt vời này!

Ngoài ra, bất kỳ ai đang thử câu trả lời được chấp nhận cũng nên cẩn thận với BlobBuilder vì tôi thấy việc hỗ trợ bị hạn chế (và được đặt tên):

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

Bạn có đang sử dụng polyfill của thư viện khác cho BlobBuilder không?


Tôi đã sử dụng Chrome mà không có polyfill và không nhớ là bắt gặp không gian tên. Tôi háo hức dự đoán canvas.toBlob()- có vẻ phù hợp hơn nhiều getAsFile.
Stoive

1
BlobBuilderdường như không được ủng hộBlob
Sandstrom

BlobBuilder không được dùng nữa và mô hình này thật tồi tệ. Tốt hơn sẽ là: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder | | window.MozBlobBuilder || window.MSBlobBuilder); các sản phẩm khai thác thử lồng nhau thực sự xấu xí và điều gì xảy ra nếu không có nhà xây dựng nào có sẵn?
Chris Hanson

Làm thế nào là khủng khiếp này? Nếu một ngoại lệ được ném và 1) BlobBuilder không tồn tại thì không có gì xảy ra và khối tiếp theo được thực thi. 2) Nếu nó tồn tại, nhưng một ngoại lệ được đưa ra thì nó không được sử dụng và dù sao cũng không nên sử dụng, vì vậy nó sẽ tiếp tục vào khối thử tiếp theo. Lý tưởng nhất là bạn sẽ kiểm tra xem Blob có được hỗ trợ trước hay không và ngay cả trước khi kiểm tra này để nhận hỗ trợ toBlob
TaylorMac

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

có thể được sử dụng mà không cần thử bắt.

Cảm ơn bạn đã kiểm tra. Công việc tuyệt vời


1
Điều này vẫn sẽ gây ra lỗi nếu trình duyệt hỗ trợ BlobBuilder không dùng nữa. Trình duyệt sẽ sử dụng phương thức cũ nếu nó hỗ trợ, ngay cả khi nó hỗ trợ phương thức mới. Điều này là không mong muốn, hãy xem cách tiếp cận của Chris Bosco bên dưới
TaylorMac

4

Câu trả lời ban đầu của Stoive có thể dễ dàng sửa chữa bằng cách thay đổi dòng cuối cùng để phù hợp với 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

Đây là phiên bản ES6 của câu trả lời của 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});
  }
}

Sử dụng:

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

Cảm ơn! @steovi cho giải pháp này.

Tôi đã thêm hỗ trợ cho phiên bản ES6 và thay đổi từ unescape sang dataURI (unescape bị phản đối).

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

làm cho nó đơn giản: 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 cung cấp cho bạn một chuỗi và bạn có thể đặt chuỗi đó vào một đầu vào ẩn.


Bạn có thể vui lòng cho một ví dụ? Tôi sẽ không tải lên chuỗi base64 (đó là những gì <input type=hidden value="data:..." />sẽ làm), tôi muốn tải lên dữ liệu tệp (giống như những gì <input type="file" />, ngoại trừ bạn không được phép đặt thuộc valuetính trên các chuỗi này).
Stoive

Đây nên là một bình luận chứ không phải là một câu trả lời. Hãy giải thích câu trả lời với lời giải thích phù hợp. @Cat Chen
May mắn

-5

Tôi đã có chính xác vấn đề tương tự như Ravinder Payal và tôi đã tìm thấy câu trả lời. Thử cái này:

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

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

2
Bạn có thực sự đề nghị sử dụng Parse.com? Bạn nên đề cập rằng câu trả lời của bạn đòi hỏi sự phụ thuộc!
Pierre Maoui 7/07/2016

2
WTF? Tại sao bất kỳ ai cũng sẽ tải mã hình ảnh base64 lên máy chủ PARSE và sau đó tải xuống? Khi chúng tôi có thể trực tiếp tải lên cơ sở64 trên máy chủ của mình và điều chính là nó lấy cùng một dữ liệu để tải lên chuỗi cơ sở hoặc tệp hình ảnh. Và nếu bạn chỉ muốn cho phép người dùng tải xuống hình ảnh, bạn có thể sử dụng điều nàywindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.