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

322
我一直在尝试重新实现一个HTML5图片上传器,就像Mozilla Hacks网站上的那个一样,但它只适用于WebKit浏览器。任务的一部分是从canvas对象中提取图像文件,并将其附加到FormData对象以进行上传。
问题在于,虽然canvas具有toDataURL函数来返回图像文件的表示形式,但FormData对象仅接受来自File API的File或Blob对象。
Mozilla的解决方案在canvas上使用了以下仅适用于Firefox的函数:
var file = canvas.mozGetAsFile("foo.png");

...这在WebKit浏览器上不可用。我能想到的最好解决方案是找到一种将数据URI转换为文件对象的方法,我认为这可能是File API的一部分,但我无论如何都找不到相应的内容。

这有可能吗?如果不行,还有其他替代方案吗?


如果您想在服务器上保存图像的DataURI:https://dev59.com/questions/V6bja4cB1Zd3GeqPbiEm#50131281 - Sibin John Mattappallil
14个回答

525

经过不断尝试,我终于自己解决了这个问题。

首先,以下代码可以将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);

37
为什么这总是发生...你花了好几个小时在 Stack Overflow 上搜索并尝试解决一个问题。然后你发布了一个问题。在一小时内,你从另一个问题的答案中得到了答案。不是我在抱怨... https://dev59.com/emDVa4cB1Zd3GeqPf7WF - syaz
1
@mimo - 它指向底层的 ArrayBuffer,然后写入到 BlobBuilder 实例中。 - Stoive
2
@stoive 那种情况下为什么不是 bb.append(ia)? - Mimo
从我的问题[这里](https://dev59.com/nI_ea4cB1Zd3GeqPMEQZ?noredirect=1#comment53171000_32662175)的评论中来到这里。请注意,我想使用文件而不是将其附加到表单中,因此我找到了这种方法:`URL.createObjectURL()`,它返回文件名作为字符串。 - axlotl
6
谢谢!这个小修正解决了我的问题。 var file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file); 注:该内容为 JavaScript 代码。 - Prasad19sara
显示剩余13条评论

151

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

4
一个想法:array=[]; array.length=binary.length; ... array[i]=bina... 等等。因此,该数组是预先分配的。这样可以避免在每次迭代时扩展数组并保存push(),而我们在这里可能处理数百万个项目(=字节),所以这很重要。 - DDS
2
在Safari上也失败了。@WilliamT的答案对于Firefox/Safari/Chrome都有效。 - ObscureRobot
2
类型:'image/jpeg' - 如果它是PNG图像或者您事先不知道图像扩展名呢? - Jasper
1
最好先创建 Uint8Array,而不是创建 Array 然后转换为 Uint8Array。 - cuixiping
为什么不从 DataURI 中读取 MIME 类型? - Alex78191
显示剩余2条评论

55

这个方案适用于 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' });
}

11
太好了!但你仍然可以保持mime字符串的动态性,就像Stoive的解决方案中一样,我想? // 分离mime组件 var mimeString = dataURI.split(',')[0] .split(':')[1] .split(';')[0] - Per Quested Aronsson
iOS6带有webkit前缀的回退怎么处理?你是如何处理这个问题的? - Michael

35

火狐浏览器有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一起处理您的画布或数据URL。
例如:
var dataurl = canvas.toDataURL('image/jpeg',0.8);
var blob = dataURLtoBlob(dataurl);
var fd = new FormData();
fd.append("myFile", blob, "thumb.jpg");

此外,你可以为非 Gecko 引擎的浏览器创建一个 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()适用于所有现代浏览器。” - Eric Simonton

34

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

但无论如何,这是另一种使用 fetch 将 base64 转换为 blob 的方法。

const url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(blob => {
  const fd = new FormData()
  fd.append('image', blob, 'filename')
  
  console.log(blob)

  // Upload
  // fetch('upload', { method: 'POST', body: fd })
})


1
什么是fetch,它与什么相关? - Ricardo Freitas
Fetch 是一种现代的 ajax 方法,你可以用它来代替 XMLHttpRequest。由于数据 URL 只是一个 URL,所以你可以使用 ajax 来获取该资源,并且可以选择将其作为 blob、arraybuffer 或文本获取。 - Endless
1
@Endless 'fetch()' 一个本地的base64字符串...真是一个聪明的技巧! - Diego ZoracKy
1
请注意,blob:data:并不是所有fetch实现都通用的。我们使用这种方法,因为我们知道我们只会处理移动浏览器(WebKit),但例如Edge并不支持它:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility - oligofren

20

感谢 @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()而不是像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更合适。 - Stoive
1
BlobBuilder 似乎已经被 Blob 取代。 - sandstrom
BlobBuilder已被弃用,这种模式很糟糕。更好的方法是: window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); 嵌套的try catch非常丑陋,如果没有可用的构建器会发生什么? - Chris Hanson
这有什么问题吗?如果抛出异常并且 1) BlobBuilder 不存在,则不会发生任何事情,继续执行下一个块。2) 如果它确实存在,但是抛出异常,则它已被弃用,不应再使用,因此它将继续进入下一个 try 块。理想情况下,您应该首先检查是否支持 Blob,甚至在此之前检查 toBlob 的支持。 - TaylorMac

6

这是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);

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

可以不使用try catch。

感谢check_ca检查,做得非常好。


1
如果浏览器支持已弃用的BlobBuilder,则仍会引发错误。即使浏览器支持新方法,它也会使用旧方法。这是不希望看到的情况,请参见Chris Bosco的方法。 - TaylorMac

5

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

对于在 atob() 上收到弃用警告的任何人 - 它在 node.js 上已被弃用,现在可以使用 Buffer.from()。在浏览器中未被弃用,要隐藏警告,只需使用 window.atob() - Chris Hayes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接