将数据URI转换为文件,然后追加到FormData


282

我一直在尝试重新实现HTML5图像上载器,例如Mozilla Hacks网站上的那个,但是它可以与WebKit浏览器一起使用。任务的一部分是从canvas对象中提取图像文件,并将其附加到FormData对象中进行上传。

问题在于,虽然canvas具有toDataURL返回图像文件表示形式的功能,但FormData对象仅接受File API中的 File或Blob对象。

Mozilla解决方案在Firefox上使用了以下仅Firefox功能canvas

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

...这在WebKit浏览器上不可用。我能想到的最好的解决方案是找到一种将Data URI转换为File对象的方法,我认为这可能是File API的一部分,但是我一生都找不到能够做到这一点的方法。

可能吗?如果没有,还有其他选择吗?

谢谢。


如果要将图像的DataURI保存在服务器中:stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

Answers:


469

在玩了几件事之后,我设法自己弄清楚了。

首先,这会将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);

28
为什么总会发生这种情况...您尝试使用SO搜索来解决这个问题。然后,您发布一个问题。一个小时之内,您就会从另一个问题中得到答案。我不能说我抱怨...... stackoverflow.com/questions/9388412/...
syaz

@stoive我可以构造Blob,但是请您解释一下如何构造S3 POSTPUTS3吗?
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等等。所以数组是预先分配的。它节省了必须在每次迭代中扩展数组的push(),并且这里我们可能要处理数百万个项目(=字节),因此很重要。
DDS 2013年

2
在Safari上对我也失败。@WilliamT。的答案适用于Firefox / Safari / Chrome。
ObscureRobot 2013年

“ binary”是一个有点误导的名称,因为它不是位数组,而是字节数组。
Niels Abildgaard 2014年

1
“ type:'image / jpeg'”-如果它是png图像,或者如果您事先不知道图像扩展名怎么办?
2014年

首先创建Uint8Array比创建Array然后转换为Uint8Array更好。
cuixiping 2015年

52

这适用于iOS和Safari。

您需要使用Stoive的ArrayBuffer解决方案,但不能如vava720所示使用BlobBuilder,因此这是两者的混搭。

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的解决方案中一样?//分离出mime组件var mimeString = dataURI.split(',')[0] .split(':')[1] .split(';')[0]
Per Quested Aronsson

带webkit前缀的iOS6的后备广告是什么?您如何处理?
2014年

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为非壁虎引擎浏览器创建方法。

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的polyfill是处理此问题的正确方法恕我直言。
Jakob Kruse

2
我想强调这篇文章的最后一件事:“现在canvas.toBlob()适用于所有现代浏览器。”
埃里克·西蒙顿

25

我的首选方式是canvas.toBlob()

但是无论如何,这是使用fetch ^^将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})
})


什么是提取及其相关性?
里卡多·弗雷塔斯

取是一个现代化的AJAX方法可以代替使用XMLHttpRequest,因为数据链接只是一个网址,你可以使用Ajax来获取该资源,你有你自己的选择来决定,如果你想把它当作BLOB,arraybuffer或文本
无尽的

1
@Endless'fetch()'本地base64字符串...一个非常聪明的技巧!
Diego ZoracKy

1
请记住,blob:data:没有得到普遍的所有fetch实现的支持。我们使用这种方法,因为我们知道,我们只会处理移动浏览器(WebKit的),但边缘例如,做不支持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使用了另一个库的polyfill?


我当时使用的是Chrome,没有Polyfills,也不记得遇到命名空间问题。我急切地期待canvas.toBlob()-这似乎比getAsFile
Stoive 2011年

1
BlobBuilder似乎有利于被废弃Blob
桑德斯卓姆

BlobBuilder已过时,此模式很糟糕。更好的是:window.BlobBuilder =(window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); 嵌套的try catch真的很丑,如果没有可用的构建器,会发生什么?
克里斯·汉森

怎么会这样 如果引发异常并且1)BlobBuilder不存在,则什么也不会发生,并执行下一个块。2)如果确实存在,但是引发了异常,则不建议使用它,并且无论如何都不应使用它,因此它将继续到下一个try块。理想情况下,您将首先检查是否支持Blob,甚至在检查toBlob支持之前进行检查
TaylorMac

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

无需尝试即可使用。

谢谢check_ca。做得好。


1
如果浏览器支持不推荐使用的BlobBuilder,这仍然会引发错误。浏览器将支持旧方法,即使它支持新方法。这是不希望的,请参阅下面的Chris Bosco的方法
TaylorMac,2014年

4

通过更改最后一行以适应Blob,Stoive的原始答案很容易修复:

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
幸运

-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?您应该提到您的答案需要依赖!
皮埃尔·

2
WTF?为什么任何人都将image64的base64代码上传到PARSE服务器然后下载?当我们可以在服务器上直接上传base64时,主要的一点是上传base64字符串或图像文件需要相同的数据。如果您只想允许用户下载图像,则可以使用它window.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.