【问题标题】:Convert Data URI to File then append to FormData将数据 URI 转换为文件,然后附加到 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 的一部分,但我一辈子都找不到这样做的东西。

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

谢谢。

【问题讨论】:

标签: javascript html webkit


【解决方案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 搜索来解决一个问题几个小时。然后你发布一个问题。在一个小时内,您会从另一个问题中得到答案。不是我在抱怨...stackoverflow.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"))
            猜你喜欢
            • 2020-04-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-06
            相关资源
            最近更新 更多