如何开发一个HTML5图片上传器

HTML5 带有一组非常棒的 API。如果你将这些 API 与 <canvas> 元素结合起来,就可以创建一个超级/现代/棒的图片上传器。本文将向你展示如何做到这一点。

所有这些技巧在 Firefox 4 中都能很好地工作。我还会描述一些替代方法来确保它在基于 Webkit 的浏览器中也能正常工作。大多数 API 在 IE 中无法正常工作,但使用普通表单作为后备非常容易。

如果你在项目中使用这些技术中的任何一项,请告诉我们!

检索图片

拖放

要上传文件,你需要一个 <input type=”file”> 元素。但你还可以允许用户将图片从桌面直接拖放到网页上。

我已经写了一篇关于 为你的网页实现拖放支持 的详细文章。

此外,还可以查看 Mozilla 关于拖放的教程

多重输入

允许用户从文件选择器中同时选择多个文件上传。


同样,这里有一篇我写的关于 多重文件选择 的文章。

预处理文件

使用 File API

(有关详细信息,请参阅 File API 文档。)

从拖放或从 <input> 元素中,你将获得一个准备使用的文件列表。

// from an input element
var filesToUpload = input.files;
// from drag-and-drop
function onDrop(e) {
  filesToUpload = e.dataTransfer.files;
}

确保这些文件实际上是图片

if (!file.type.match(/image.*/)) {
  // this file is not an image.
};

显示缩略图/预览

这里有两个选项。你可以使用 FileReader (来自 File API) 或使用新的 createObjectURL() 方法。

createObjectURL()

var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);

FileReader

var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);

使用画布

一旦你在一个 <img> 元素中拥有图片预览,你就可以将此图片绘制在一个 <canvas> 元素中来预处理文件。

var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);

调整图片大小

人们习惯于直接从他们的相机上传图片。这会生成高分辨率和极重的 (几兆字节) 文件。根据用途,你可能需要调整这些图片的大小。一个超级简单的技巧是简单地使用一个小的画布 (例如 800×600),然后将图片标签绘制到此画布中。当然,你需要更新画布的尺寸以保持图片的比例。

var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;

if (width > height) {
  if (width > MAX_WIDTH) {
    height *= MAX_WIDTH / width;
    width = MAX_WIDTH;
  }
} else {
  if (height > MAX_HEIGHT) {
    width *= MAX_HEIGHT / height;
    height = MAX_HEIGHT;
  }
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);

编辑图片

现在,你在画布中拥有你的图片。基本上,可能性是无限的。假设你想应用一个棕褐色滤镜。

var imgData = ctx.createImageData(width, height);
var data = imgData.data;
var pixels = ctx.getImageData(0, 0, width, height);
for (var i = 0, ii = pixels.data.length; i < ii; i += 4) {
    var r = pixels.data[i + 0];
    var g =pixels.data[i + 1];
    var b = this.pixels.data[i + 2];
    data[i + 0] = (r * .393) + (g *.769) + (b * .189);
    data[i + 1] = (r * .349) + (g *.686) + (b * .168)
    data[i + 2] = (r * .272) + (g *.534) + (b * .131)
    data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);

使用 XMLHttpRequest 上传

现在你在客户端已经加载了图片,最终你想将它们发送到服务器。

如何发送画布

同样,你有两个选项。你可以 将画布转换为数据 URL 或 (在 Firefox 中) 从画布中创建一个文件

canvas.toDataURL()

var dataurl = canvas.toDataURL("image/png");

从画布中创建一个文件

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

原子上传

允许用户同时上传一个文件或所有文件。

显示上传进度

使用上传事件创建一个进度条

xhr.upload.addEventListener("progress", function(e) {
  if (e.lengthComputable) {
    var percentage = Math.round((e.loaded * 100) / e.total);
    // do something
}, false);

使用 FormData

你可能不想只是上传文件 (这可以通过 xhr.send(file) 很容易完成),而是添加一些辅助信息 (如密钥和名称)。

在这种情况下,你需要通过一个 FormData 对象创建一个 multipart/form-data 请求。(请参阅 Firefox 4: 使用 FormData 简化 JS 表单处理。)

var fd = new FormData();
fd.append("name", "paul");
fd.append("image", canvas.mozGetAsFile("foo.png"));
fd.append("key", "××××××××××××");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://your.api.com/upload.json");
xhr.send(fd);

打开你的 API

也许你想允许其他网站使用你的服务。

允许跨域请求

默认情况下,你的 API 只能从你的域创建的请求访问。如果你想允许人们使用你的 API,请在你的 HTTP 头中允许 Cross-XHR。

Access-Control-Allow-Origin: *

你还可以只允许一个预定义的域列表。

阅读关于 跨域资源共享 的内容。

postMessage

(感谢 Daniel Goodwin 提供的这个提示。)

此外,请监听从 postMessage 发送的消息。你可以允许人们通过 postMessage 使用你的 API。

document.addEventListener("message", function(e){
    // retrieve parameters from e.data
    var key = e.data.key;
    var name = e.data.name;
    var dataurl = e.data.dataurl;
    // Upload
}
// Once the upload is done, you can send a postMessage to the original window, with URL

以上就是所有内容。如果你有任何其他技巧要分享,请随时发表评论。

享受;)

关于 Paul Rouget

Paul 是 Firefox 开发者。

更多由 Paul Rouget 撰写的文章…


40 条评论

  1. Mathias Bynens

    好文章!我希望有演示。

    2011年1月5日 下午 4:16

  2. stefs

    实际上,几个月前我创建了一个类似的拖放图片上传器,但当我无法指定 jpeg 质量时,就放弃了这个项目。

    http://www.w3.org/TR/html5/the-canvas-element.html#dom-canvas-todataurl
    image/jpeg: 如果第二个参数是 0.0 到 1.0(包含)之间的数字,则必须将其视为所需的质量级别。如果它不是数字,或超出该范围,则用户代理必须使用其默认值,就好像省略了参数一样。

    关键是,直接来自相机的多兆字节高分辨率 jpeg 文件与调整大小后的 png 或 jpeg 文件大小差不多(如果你使用 jpeg 作为格式,默认情况下质量为 100%,使得文件非常大)。所以如果源文件大小是 3mb,调整大小后的版本大小仍然差不多,而不是 200kb。

    我已经在这里报告了这个错误:https://bugzilla.mozilla.org/show_bug.cgi?id=564388 - 不确定它是否已经可以工作了。

    2011年1月5日 上午 6:01

  3. John Drinkwater

    如果 Firefox 没有关于 file.type 测试的错误,它对我来说就很有用。
    任何没有扩展名('.png' 等)的文件,它都会无法通过是图片的测试。
    请参阅 https://bugzilla.mozilla.org/show_bug.cgi?id=596968

    2011年1月5日 上午 6:06

  4. Bohdan Ganicky

    嗨,
    感谢你的文章。我最近已经实现了一些类似的东西。它是一个私人的管理界面,所以我可以使用所有新的 API。我真的很高兴我们现在可以通过 XHR 上传文件。我现在正在寻找一种方法,以某种方式“增强”jQuery .ajax() 方法以与文件一起工作。
    我在此处制作了一个快速屏幕录像,展示了我的实现的实际效果:http://www.youtube.com/watch?v=xSwAeJvZZ78

    你可以使用文件输入(多重)或从文件浏览器或桌面拖放图片。然后图片将通过 XHR(逐个)发送到服务器,服务器返回它们的处理结果。然后你可以点击图片并进行一些编辑等操作。

    在 Firefox 4 和最新的“webkits”中工作。

    2011年1月5日 上午 6:23

    1. Han Lin Yap

      我创建了一个 jQuery 插件,使用 jQuery.ajax() 上传文件。
      https://github.com/codler/jQuery-Ajax-Upload

      2011年1月6日 上午 4:13

  5. arno

    不幸的是,使用 gecko 调整大小的图片通常看起来很模糊:与 gimp、convert、phatch 等调整大小工具相比,gecko 图片调整大小工具的质量并不理想。

    例如,我拍摄了这张图片
    http://upload.wikimedia.org/wikipedia/commons/0/00/Salar_de_Uyuni_D%C3%A9cembre_2007_-_Panorama_1_edit.jpg
    我使用 gecko 和 gimp 将它调整为 800px 宽度。以下是结果。
    http://renevier.net/pics/salar_resize_with_gecko.jpeg
    http://renevier.net/pics/salar_resize_with_gimp.jpg

    你可以使用这个在线调整大小工具尝试更多操作
    http://renevier.net/misc/resizeimg.html

    2011年1月5日 上午 6:27

  6. JKM

    “调整图片大小”的逻辑是错误的。考虑一个 1000×860 的图片:它最终会变成 800×688。 “if (width > height) {” 检查仅在你的最大尺寸是正方形时才有效。否则你需要比较纵横比(MAX_WIDTH/MAX_HEIGHT 与 width/height)。

    2011年1月5日 上午 7:06

    1. Mikkel

      最简单的方法是找到每个尺寸所需的比例,选择最小值,然后将该值乘以所有尺寸,即(伪代码)

      scale = min(max_height/height, max_width/width)
      if scale<1
      height *= scale
      width *= scale

      2011年1月5日 上午 9:26

  7. Richard

    谢谢!那么这在 iPhone Safari 和 Android Chrome 中也可以使用吗?

    2011年1月5日 上午 8:52

  8. tehk

    为什么要保留没有扩展名的图片文件呢?希望每个人都对你的所有文件进行 mime 类型检查?哈哈

    2011年1月5日 上午 8:56

    1. thinsoldier

      我看到很多 Windows 用户关闭了文件扩展名,过去也有很多 Mac 用户拥有不少没有文件扩展名的图片。

      2011年1月5日 下午 1:42

  9. Jesper Kristensen

    如果可以展示一个真正有用的例子,那就太好了。例如:如何检测拖放功能,以便提供回退选项,或者只是给出正确的指示?此外,在大多数情况下,复制粘贴比拖放更有用。如何实现复制粘贴功能?

    2011年1月5日 上午 9:51

  10. Paul Irish

    太棒了。我已经将这个列入了我的待办事项清单… 让拖放文件上传功能在所有地方都能正常工作是一个优先事项… 而且很容易!

    让我加入其他人一起请求演示。这是一个常见的方案,能够拥有它并进行迭代将非常棒。

    2011年1月5日 上午 10:11

  11. thinsoldier

    我同意 Jesper Kristensen 的观点。我看到很多使用我的电脑(Windows)和笔记本电脑(OS X)的人,甚至连在纸牌游戏中拖放卡片都感到困难。

    他们唯一都能掌握的计算技能就是复制粘贴!

    即使我本人也更喜欢将屏幕截图复制粘贴到 Mail.app 中,而不是将它们保存到文件,然后找到它们并将其附加或拖放到 Mail.app 中。当然,这样会生成一个很大的 .png 附件,但在我大多数情况下,这种工作流程更快。

    2011年1月5日 下午 1:40

  12. Justin Dolske

    我认为 Imgur (http://imgur.com/) 使用了一种类似的技术,尽管我承认没有真正看过他们的代码。:) 只需访问该 URL 并将图像拖放到浏览器窗口中。

    2011年1月5日 下午 2:45

    1. Paul Rouget

      我实际上正在与 Imgur 开发人员合作。

      2011年1月6日 上午 3:35

  13. 匿名

    不要使用进度条,而是使用图像本身:绘制一条明亮的直线从画布下方移动,就像“扫描”图像一样。

    2011年1月5日 下午 2:50

  14. Kai

    如果你在一个将被上传图片(图像个人资料图片)替换的现有图片之上创建一个画布元素,并让它随着上传进度的进行垂直调整大小,效果会很不错。因此,一开始画布的高度为 0%,最后高度为 100%,覆盖之前的个人资料图片。

    2011年1月5日 下午 3:07

  15. Daniel Goodwin

    继续谈论 postMessage,我已经详细介绍了如何在现有 API 之外使用 postMessage(以图像服务为例)。

    http://dsg.posterous.com/setting-up-your-api-to-accept-html5-postmessa

    2011年1月5日 下午 6:39

  16. dimkalinux

    我在 http://pic.lg.ua/ 上实现了 HTML5 图像上传器。
    在 Firefox4 和 WebKit 上运行良好。

    2011年1月6日 上午 10:47

  17. Johannes Fahrenkrug

    非常棒的文章,还有很棒的棕褐色滤镜效果。谢谢!

    2011年1月6日 上午 11:58

  18. Ryan Gasparini

    为什么 Firefox 实现的是 window.URL 对象,而不是一个全局 createObjectURL 方法?

    2011年1月6日 下午 6:22

  19. Brian Grinstead

    不错 - 我之前在一个名为 FileReader.js 的包装器中实现了其中一些文件读取和图像处理功能 https://github.com/bgrins/filereader.js

    在阅读这篇文章以及所有关于想要上传演示的评论后,我认为我可以将一个演示整合起来。它接受拖放或输入选择,并将为图像类型创建缩略图。然后,你可以选择将每个文件上传到服务器。

    它当然还需要一些工作,但我认为它在标准 API 中添加了一些关于文件读取的缺失功能,我很乐意接受任何贡献或反馈。

    演示
    http://bgrins.github.com/filereader.js/

    如果你有兴趣,还可以查看我最初为其制作它的项目:Instant Sprite – CSS Sprite Generator

    2011年1月6日 下午 10:11

  20. Loc

    这太棒了。我知道它在 Firefox 中运行,但在 IE、Chrome 和 Safari 中也能运行吗?

    2011年1月13日 下午 1:15

  21. Stephen Fluin

    我似乎遇到了一个问题。图像似乎没有在拖放事件中完全加载。当我查看 img.height 或 img.width 时,它们都是 0,但如果我等待(例如使用 alert),然后再次检查,它们就会被填充。

    还有其他人遇到这个问题吗?

    2011年1月15日 上午 9:12

  22. Simon

    有什么提示/想法可以将新的 Blob 接口用于上传(暂停/恢复)?

    2011年1月15日 上午 9:41

  23. Hari

    mozGetAsFile 方法的 W3 标准是什么?
    ” fd.append(“image”, canvas.mozGetAsFile(“foo.png”)); ”

    还是说这只是一个 Firefox 才能使用的方法来上传图像?

    2011年2月25日 下午 1:40

    1. Paul Rouget

      只需发送 dataURL 即可。

      2011年2月26日 上午 5:29

  24. Simon

    @Hari img.src = canvas.toDataURL() 吗?

    2011年2月26日 上午 1:29

  25. Simon

    我创建了一个 带有拖放功能的多文件上传器 的演示,它使用新的 Blob 接口的 slice 方法来允许你暂停/恢复上传。你可以查看上传器的演示,或者 查看功能列表

    2011年3月4日 上午 4:59

  26. Daryl

    这很有趣,除了我最终得到了巨大的图像尺寸。如果我尝试上传一个 53k 的图像,我将它添加到画布中,将其调整为更小的尺寸,然后使用 canvas.mozGetAsFile 和 XMLHttpRequest 将它上传到服务器。

    当我将其发布到服务器时,文件大小为 900k。

    是否有一个我不知道的质量设置?即使在 100% 的情况下,对于我拥有的图像大小来说,这似乎也太大了。

    2011年4月23日 下午 6:57

  27. stefs

    @daryl:规范中确实有一个质量设置,但目前只有少数浏览器支持它。Firefox 开发人员正在努力解决这个问题(请参阅 https://bugzilla.mozilla.org/show_bug.cgi?id=564388)。

    2011年4月25日 下午 3:51

  28. CobaltBlueDW

    我不仅非常感谢你为撰写这篇文章所付出的努力,而且你做得非常出色,这在广阔的互联网领域中很少见。

    干得好!

    2012年2月10日 上午 11:43

  29. CobaltBlueDW

    使用略少代码在两个方向进行调整大小。(假设输入已验证)

    if(old.height/old.width – pref.height/pref.width > 0){
    new.width = pref.height/old.height * old.width;
    new.height = pref.height;
    }else{
    new.height = pref.width/old.width * old.height;
    new.width = pref.width;
    }

    2012年2月11日 下午 7:57

  30. Sabith Pocker

    var reader = new FileReader();
    reader.onload = function(e) {
    img.src = e.target.result;
    delete this;
    }
    img.onload = function() {
    // 加载图像后需要执行的操作
    }
    reader.readAsDataURL(file);
    分配 dataURL 实际上不会加载图像,我尝试在 reader.onload 中使用图像,但图像在那一刻并没有加载,上面的代码对于在计算中使用已加载图像的宽度和高度的人来说很有用。
    很棒的教程和演示,非常感谢 Paul…

    2012年4月9日 上午 0:05

  31. Ashley Sheridan

    Stephen Fluin,我也遇到了这个问题,但只需将你的代码放在 img 对象的 onload 处理程序中即可轻松解决。浏览器只需要一点时间来复制图像数据。

    2012年10月23日 上午 8:08

  32. OSP

    在设置图像源并在设置图像 src 后立即调整大小时,请注意异步图像加载。

    2013年3月7日 上午 5:46

  33. mete kavruk

    对于经验丰富的程序员来说,本教程中的一切看起来都很完美,但对于像我这样的新手来说却不是这样。我正在尝试制作一个没有拖放区域的多文件上传器,但我找不到一个有效的演示来获取代码并进行修改,因为我只有修改的能力。

    2013年4月5日 下午 1:04

    1. Sabith Pocker

      那么你有 4 个选择。(据我所知)
      1. 在其他网站上查找即时代码。
      2. 等待一些即时代码可用。
      3. 继续学习代码,摆脱新手的状态。
      4. 雇佣专家来完成。

      只是想指出你并没有迷路,你也不是一个毫无价值的新手。
      和平。

      2013年4月8日 下午 2:13

      1. mete kavruk

        感谢你的回复。问题是我现在必须成为一个单兵作战的人,因为我正在开发一个独特的项目,我必须是唯一负责图形、编程和营销的人。因此,我解决问题的能力下降了。这听起来可能很疯狂,但在土耳其,即使是大公司也使用现成的脚本。例如,最大的门户网站都是用几十美元的脚本开发的,已经有几年的历史了。如果你是一位设计师,你每个月的薪水只有 400 美元,最多 1500 美元。是的,有一些富有的程序员,但他们是一千人中的一人,服务于非常利基的领域,或者一些幸运的人通过几家代理机构转售一些经过修改的现成脚本,从而赚得巨额财富。

        2013年4月9日 上午 9:34

本文的评论已关闭。