将HTML5画布转换为可上传的文件?

94

标准的HTML文件上传工作方式如下:

<g:form method="post" accept-charset="utf-8" enctype="multipart/form-data"  
     name="form" url="someurl">

    <input type="file" name="file" id="file" />

</form>

在我的情况下,我将一张图片加载到HTML5画布中,并希望将其作为文件提交到服务器。

我可以这样做:

var canvas; // some canvas with an image
var url = canvas.toDataURL();

这给了我一个以base64形式的image/png图像。

我该如何以与input type="file"相同的方式将base64图像发送到服务器?

问题在于,base64文件与input type="file"内部的文件类型不同。

我能否以某种方式转换base64,使得服务器上的文件类型与之相同?


3
canvas.toBlob():将 canvas 元素中的内容转换为 Blob(二进制大对象) 对象。了解更多请访问 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob - Ray Nicholus
2
@RayNicholus,我该如何将Blob放入文件输入框中? - Michael
1
你不需要这样做。通过ajax请求发送它。 - Ray Nicholus
2
@RayNicholus 那我该如何将 Blob 传输到服务器? - Michael
1
@RayNicholus 我总是得到没有 toBlob() 方法的提示! - Michael
显示剩余9条评论
8个回答

78

出于安全原因,您无法直接设置文件输入元素的值。

如果要使用文件输入元素:

  1. 从画布创建图像(如您所做的)。
  2. 在新页面上显示该图像。
  3. 让用户右键保存到本地驱动器中。
  4. 然后他们可以使用您的文件输入元素上传新创建的文件。

或者,您可以使用Ajax来POST画布数据:

您问了blob相关的问题:

var blobBin = atob(dataURL.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
    array.push(blobBin.charCodeAt(i));
}
var file=new Blob([new Uint8Array(array)], {type: 'image/png'});


var formdata = new FormData();
formdata.append("myNewFileName", file);
$.ajax({
   url: "uploadFile.php",
   type: "POST",
   data: formdata,
   processData: false,
   contentType: false,
}).done(function(respond){
  alert(respond);
});

注意:blob通常在最新的浏览器中得到支持。


2
我想将图片发送到服务器! - Michael
3
明白了!如果你想使用html文件输入元素,可以使用我回答中的方法。或者,你可以使用Ajax将图像发送到服务器。这里有一个关于如何使用该替代方法的stackoverflow帖子:https://dev59.com/t2Ys5IYBdhLWcg3wBvb7 - markE
1
它在Chrome中可以工作,但在Firefox中无法工作。服务器端数据始终为空。有什么想法吗? - Michael
在Firefox中的问题是脚本会崩溃。 - Michael
如果图片太大(例如7MB),FireFox会出现故障。 - Michael
显示剩余3条评论

45

需要将画布图像转换为base64,然后从base64转换为二进制。使用.toDataURL()dataURItoBlob()实现此操作。

这是一个相当棘手的过程,需要将几个SO答案、各种博客文章和教程拼凑在一起。

我创建了一个教程,带您完成整个过程

针对Ateik的评论,这里有一个示例,可以复制原始帖子,以防您无法查看原始链接。您还可以在此处fork我的项目。

代码很多,但我正在做的核心是获取画布元素:

<canvas id="flatten" width="800" height="600"></canvas>

将其上下文设置为2D

var snap = document.getElementById('flatten');
var flatten = snap.getContext('2d');

Canvas => Base64 => Binary

function postCanvasToURL() {
  // Convert canvas image to Base64
  var img = snap.toDataURL();
  // Convert Base64 image to binary
  var file = dataURItoBlob(img);
}

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

如果您只需要 base64,则可以停在那里,而在我的情况下,我需要再次转换为二进制,以便我可以传递数据到 Twitter(使用 OAuth)而无需使用数据库。结果发现,您可以在 Twitter 上发布二进制文件,这非常酷,Twitter 会将其转换回图像。


1
帮了我很多!谢谢! - ryanc
哎呀,看看“toBlob”吧。不再需要额外的步骤了 :) - Michael Chaney
1
@Michael Chaney 这是 toBlob 之前的做法,不要居高临下,如果现在有更简单的方法,请写一个答案。 - Pixelomo
2
@pixelomo 不是要表现得高傲。但你说得对,它值得一个更新的答案。 - Michael Chaney
1
谢谢,你帮我省了很多时间 :) - Marcin Żmigrodzki
显示剩余5条评论

43

最后这就是对我有效的方法。

canvas.toBlob((blob) => {
  let file = new File([blob], "fileName.jpg", { type: "image/jpeg" })
}, 'image/jpeg');


3
这个答案救了我的一天!谢谢@Reinier68 - Vikram Ray

16

目前在规范中(截至2017年4月支持度很低)

Canvas.toBlob();

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob

编辑:

该链接提供了一个 polyfill(从措辞上看似乎较慢),其代码与 @pixelomo 的答案大致等效,但具有与本地 toBlob 方法相同的 API:

 

基于 toDataURL 的低性能 polyfill:

if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
    value: function (callback, type, quality) {
      var canvas = this;
      setTimeout(function() {

    var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] ),
        len = binStr.length,
        arr = new Uint8Array(len);

    for (var i = 0; i < len; i++ ) {
      arr[i] = binStr.charCodeAt(i);
    }

    callback( new Blob( [arr], {type: type || 'image/png'} ) );

      });
    }
  });
}

使用方式如下:

canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG at 95% quality
或者
canvas.toBlob(function(blob){...}); // PNG

9
另一种解决方案:在隐藏字段中发送var url中的数据,解码并保存到服务器上。
Python Django示例:
if form.is_valid():
    url = form.cleaned_data['url']
    url_decoded = b64decode(url.encode())        
    content = ContentFile(url_decoded) 
    your_model.model_field.save('image.png', content)

8
const canvas = document.querySelector("canvas");
canvas.toBlob(blob => {
  const file = new File([blob], "image.png");
});

1
请为上面添加的代码片段提供一些背景信息。 - Apoorva Chikara

4

我过去经常很简单地做这件事

var formData = new FormData(),
    uploadedImageName = 'selfie.png';

canvas.toBlob(function (blob) {
    formData.append('user_picture', blob, uploadedImageName);
    $.ajax({
        data: formData,
        type: "POST",
        dataType: "JSON",
        url: '',
        processData: false,
        contentType: false,
    });
});

0

使用toBlob和TypeScript的2023年答案:

const dataBlob = await new Promise<Blob | null>(
  (resolve) => canvas.toBlob(
    (blob) => resolve(blob),
    "image/png",
  )
);

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