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

Posted

技术标签:

【中文标题】将数据 URI 转换为文件,然后附加到 FormData【英文标题】:Convert Data URI to File then append to FormData 【发布时间】:2011-06-27 07:13:59 【问题描述】:

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

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

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

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

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

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

谢谢。

【问题讨论】:

如果要将图片的DataURI保存到服务器:***.com/a/50131281/5466401 【参考方案1】:

在玩了一些东西之后,我自己设法解决了这个问题。

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

【讨论】:

为什么总是会发生这种情况...你试图用 SO 搜索来解决一个问题几个小时。然后你发布一个问题。在一个小时内,您会从另一个问题中得到答案。不是我在抱怨...***.com/questions/9388412/… @stoive 我可以构建 Blob,但您能解释一下如何将 POSTPUT 构建到 S3 吗? @mimo - 它指向底层的ArrayBuffer,然后将其写入BlobBuilder 实例。 @stoive 那为什么不是 bb.append(ia)? 谢谢!这通过一个小的修正解决了我的问题var file = new File( [blob], 'canvasImage.jpg', type: 'image/jpeg' ); fd.append("canvasImage", file); 【参考方案2】:

现在不推荐使用 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');

【讨论】:

只是一个想法:array=[]; array.length=binary.length; ... array[i]=bina... 等等。所以数组是预先分配的。它节省了每次迭代都必须扩展数组的 push(),而且我们在这里可能处理数百万个项目(=字节),所以这很重要。 在 Safari 上对我来说也失败了。 @WilliamT。的答案适用于 Firefox/Safari/Chrome。 "binary" 是一个有点误导的名字,因为它不是一个位数组,而是一个字节数组。 "type: 'image/jpeg'" - 如果是 png 图像或者如果您事先不知道图像扩展名怎么办? 先创建 Uint8Array 比先创建 Array 然后再转换为 Uint8Array 好。【参考方案3】:

这个适用于 ios 和 Safari。

您需要使用 Stoive 的 ArrayBuffer 解决方案,但不能使用 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' );

【讨论】:

太棒了!但是我想你仍然可以保持 mime 字符串动态,就像在 Stoive 的解决方案中一样? // 分离出 mime 组件 var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] 带有 webkit 前缀的 iOS6 的回退是什么?你是怎么处理的?【参考方案4】:

Firefox 有 canvas.toBlob() 和 canvas.mozGetAsFile() 方法。

但其他浏览器没有。

我们可以从canvas中获取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
);

【讨论】:

这里提到的 canvas.toBlob 的 polyfill 是处理这个问题恕我直言的正确方法。 我想强调这篇文章的最后一件事:“现在 canvas.toBlob() 适用于所有现代浏览器。”【参考方案5】:

我的首选方式是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)
)

【讨论】:

什么是 fetch 以及它的相关性如何? Fetch 是一种现代 ajax 方法,您可以使用它来代替 XMLHttpRequest,因为数据 url 只是一个 url,您可以使用 ajax 来获取该资源,并且您可以选择是否需要它作为 blob、arraybuffer 或文本 @Endless 'fetch()' 一个本地 base64 字符串...一个非常聪明的 hack! 请记住,blob:data: 并非所有 fetch 实现都普遍支持。我们使用这种方法,因为我们知道我们只会处理移动浏览器 (WebKit),但 Edge 例如不支持它:developer.mozilla.org/en-US/docs/Web/API/…【参考方案6】:

感谢@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);

【讨论】:

【参考方案7】:

不断发展的标准看起来是canvas.toBlob(),而不是 Mozilla 冒险猜测的 canvas.getAsFile()。

我还没有看到任何浏览器支持它:(

感谢这个精彩的帖子!

此外,任何尝试接受的答案的人都应该小心使用 BlobBuilder,因为我发现支持受到限制(和命名空间):

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

您是否为 BlobBuilder 使用了另一个库的 polyfill?

【讨论】:

我使用的是没有 polyfills 的 Chrome,并且不记得遇到过命名空间。我热切期待canvas.toBlob() - 它似乎比getAsFile 更合适。 BlobBuilder 似乎被Blob 弃用了 BlobBuilder 已被弃用,这种模式很糟糕。更好的是:window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder);嵌套的 try catch 真的很难看,如果没有可用的构建器会发生什么? 这有多糟糕?如果抛出异常并且 1) BlobBuilder 不存在,则不会发生任何事情并执行下一个块。 2) 如果它确实存在,但是抛出了一个异常,那么它被弃用并且不应该被使用,所以它继续到下一个 try 块。理想情况下,您会首先检查是否支持 Blob,甚至在此检查 toBlob 支持之前。【参考方案8】:
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

可以在没有 try catch 的情况下使用。

感谢 check_ca。做得好。

【讨论】:

如果浏览器支持已弃用的 BlobBuilder,这仍然会引发错误。如果浏览器支持旧方法,它将使用旧方法,即使它支持新方法。这是不希望的,请参阅下面 Chris Bosco 的方法【参考方案9】:

通过更改最后一行以适应 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);

【讨论】:

【参考方案10】:

这是Stoive's answer的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);

【讨论】:

【参考方案11】:

谢谢! @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);

【讨论】:

【参考方案12】:

简单点: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;

【讨论】:

【参考方案13】:

toDataURL 为您提供一个字符串,您可以将该字符串放入隐藏的输入中。

【讨论】:

你能举个例子吗?我不想上传 base64 字符串(&lt;input type=hidden value="data:..." /&gt; 会这样做),我想上传文件数据(就像 &lt;input type="file" /&gt; 所做的那样,除非你不能设置 @987654323 @ 属性)。 这应该是评论而不是答案。请用适当的解释详细说明答案。 @陈猫【参考方案14】:

我遇到了与 Ravinder Payal 完全相同的问题,并且我找到了答案。试试这个:

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

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

【讨论】:

你真的建议使用 Parse.com 吗?你应该提到你的答案需要依赖! WTF ?为什么有人会将图像的base64代码上传到PARSE服务器然后下载?当我们可以直接在我们的服务器上上传base64时,主要是上传base64字符串或图像文件需要相同的数据。如果你只是想让用户下载图像,你可以使用这个window.open(canvas.toDataURL("image/jpeg"))

以上是关于将数据 URI 转换为文件,然后附加到 FormData的主要内容,如果未能解决你的问题,请参考以下文章

将content:// uri转换为文件路径

将 java 应用程序转换为 restful uri

将UIImage转换为数据URI

Terraform - 创建 EBS 的快照,然后将快照转换为 EBS 并附加到 EC2

使用 typescript 使用 data-uri 将图像转换为 base64

Cordova Camera Plugin Android 将额外信息附加到 FILE_URI 的末尾