使用拖放、FileAPI 和 XMLHttpRequest 实现交互式文件上传

在之前的文章中,我们展示了如何通过 input 标签拖放机制 来访问文件。在这两种情况下,您都可以使用 XMLHttpRequest 来上传文件并跟踪上传进度。

演示

如果您正在运行最新的 Firefox 3.6 测试版,请查看我们的 文件上传演示.

上传

XMLHttpRequest 将以二进制数组的形式将给定文件发送到服务器,因此您首先需要使用 File API 将文件内容读入二进制字符串。由于 拖放input 标签都允许您一次处理多个文件,因此您需要创建与文件数量相同的请求。

var reader = new FileReader();
reader.onload = function(e) {
  var bin = e.target.result;
  // bin is the binaryString
};
reader.readAsBinaryString(file);

文件读取完成后,使用 XMLHttpRequest 将其发送到服务器

var xhr = new XMLHttpRequest();
xhr.open("POST", "upload.php");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
xhr.sendAsBinary(bin);

您可以选择在请求过程中发生特定事件(例如错误、成功或中止)时收到通知(有关更多详细信息,请参阅 MDC 文档)。

跟踪上传进度

progress 事件提供了已上传二进制内容的大小。这使您可以轻松地计算已上传的文件百分比。

以下示例显示了到目前为止已上传的文件百分比

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

关于 Paul Rouget

Paul 是一名 Firefox 开发人员。

Paul Rouget 的更多文章...


27 条评论

  1. Michael Irwin

    哇!这看起来非常棒。我迫不及待地等待最终版本发布!:-)

    2009 年 12 月 15 日 下午 11:40

  2. Ryan

    我喜欢画布加载器,很性感。

    我之前做了一个拖放上传器,以下是一些观察结果:http://www.thecssninja.com/javascript/fileapi

    – 将函数传递给进度事件侦听器并使用 e.target 访问加载器容器会随机失败,并且会失去对自身的引用。为了解决这个问题,我使用侦听器内的匿名函数,而不是将任务传递给外部函数。这样一来,我可以直接访问容器,而无需使用 e.target。

    – 另一个问题是,如果我将 Windows Vista 示例图像拖入,它们都会开始正常上传,但是当它上传到两个特定的文件时,就会崩溃并抛出异常

    未捕获的异常:[异常...“组件返回失败代码:0x80004003 (NS_ERROR_INVALID_POINTER) [nsIDOMFileReader.readAsBinaryString]” nsresult: “0x80004003 (NS_ERROR_INVALID_POINTER)” 位置: “JS 帧 :: http://thecssninja.com/demo/drag-drop_upload/v2/ :: 匿名 :: 第 147 行” 数据:无]

    总是只有两个文件会失败,而且它们是示例图像中两个最大的文件。对于大于 ~550kb 的图像,似乎会发生零星的失败,但这也会取决于我正在上传的图像数量。

    2009 年 12 月 15 日 下午 11:14

    1. Mark S

      Ryan,你有没有解决“组件返回失败代码:0x80004003 (NS_ERROR_INVALID_POINTER) [nsIDOMFileReader.readAsBinaryString]” nsresult: “0×80004003 (NS_ERROR_INVALID_POINTER)” 位置:问题?

      我打算尝试使用 /catch 来捕获它,但遗憾的是,自这条评论发布以来已经过去了一年,但似乎没有人修复这个 bug。

      2010 年 10 月 28 日 下午 12:37

      1. Ryan

        Mark,我有一段时间没有关注这个问题了。一个可能的解决方案(虽然只在 FF4 中有效)是使用 createBlobURL[1] 方法,这样您就可以链接到文件并显示它,而无需将其全部加载到内存中。直接将大量数据加载到内存中可能是导致异常的原因。Paul,如果您有更好的见解,请告知我们。

        [1] https://mdn.org.cn/en/DOM/window.createBlobURL

        2010 年 10 月 28 日 下午 3:29

        1. Mark S

          FF4 无法满足我的需求 -)
          我正在尝试检查这些状态并制定解决方法,以便自动重试上传。
          非常感谢,您的网站做得很好,如果您发现任何问题,请告诉我。

          2010 年 10 月 28 日 下午 3:50

  3. Jose

    有谁知道是否可以用相同的方式下载文件?或者,这是否是未来版本中才会实现的功能(如果有的话)?这个 bug https://bugzilla.mozilla.org/show_bug.cgi?id=338478 是否就是这种情况?

    2009 年 12 月 16 日 上午 3:29

  4. thinsoldier

    在未来的文章中,您应该展示如何将一张 500 万像素的图片拖放,并使用画布将其调整大小,分别生成 800×600 和 100×75 的缩略图。然后将这两个画布生成的图像上传到服务器,而不是上传巨大的原始图像。

    2009 年 12 月 17 日 上午 9:12

  5. iNsuRRecTiON

    您好,

    不错,但是您无法从拖放区域中删除单个图片,如果您不想发送该图片。

    例如,您选择了 15 张图片,但随后在拖放区域的预览中,您不想发送其中两张。
    现在没有办法删除这两张图片。
    您必须重新拖放或打开文件对话框,而无需这两张图片,这是一种糟糕的用户体验!

    此致,

    来自德国的 iNsuRRecTiON

    2009 年 12 月 18 日 下午 1:35

  6. Chrom

    @insurrection
    可以通过添加一个“垃圾桶”来轻松解决,您可以将添加的文件拖放到其中。

    2009 年 12 月 29 日 上午 4:48

  7. Christopher

    我理解正确吗?所有文件都是通过浏览器的内存进行传输的,因为它们以二进制字符串的形式在 JavaScript 中可用?
    我认为,如果我通过 FileReader 读取(或上传)一个 100 MB 的文件,我的浏览器内存使用量会相应增加?!

    这是预期的行为吗?

    2009 年 12 月 29 日 下午 12:53

  8. Tom

    +1 支持 thinsoldier 的建议。

    我很想看到上传前调整大小的示例。目前,这可以通过 Gears 来实现(例如 Google Wave)。但我非常希望看到如何使用 File API 来实现。

    感谢您所做的出色工作。

    2010 年 1 月 21 日 上午 1:59

  9. andym801

    thinsoldier 的建议很好,但是你们把拖放上传系统的简单性复杂化了。也许将来他们会展示一个拖放图片调整大小器。

    我希望看到的是,在我的图片上显示一个绿色勾号或红色“x”来确认上传是否成功。如果您要上传大量大图片,并且想离开电脑,这将非常有用。在这种情况下,您将无法看到小的进度环。

    2010 年 1 月 21 日 下午 7:40

  10. […] 而且根据新的规范。这将使文件上传变得更加容易。现在,网上已经有一些 [2] 例子了。但我只是想亲自动手 […]

    2010 年 1 月 24 日 下午 4:02

  11. […] 的 API,首先展示了多个文件上传,然后展示了拖放上传界面,接下来添加了进度信息(虽然这对我来说不起作用),然后从 JPEG 图像中读取 EXIF 数据。您可以想象 […]

    2010 年 1 月 25 日 下午 4:37

  12. […] 上传 https://hacks.mozilla.ac.cn/2009/12/uploading-files-with-xmlhttprequest/ http://demos.hacks.mozilla.org/openweb/uploadingFiles/ (非常 […]

    2010 年 1 月 26 日 上午 4:32

  13. Preben Borch

    是否有一些用于接收文件的服务器端 PHP 脚本示例?form 方法按预期工作,但是当我尝试使用 XMLHttpRequest 方法时,PHP 脚本中没有收到任何内容?我是否遗漏了什么?

    2010 年 1 月 28 日 上午 8:22

  14. Ryan

    本文深入探讨了如何处理从拖放操作中上传文件。 http://www.appelsiini.net/2009/10/html5-drag-and-drop-multiple-file-upload

    2010 年 1 月 28 日 下午 4:22

  15. Preben Borch

    我将标准输入转储到一个文件中,它就在那里。如果有一个更完整的示例,那就太好了 - 在 url 中发送文件名会很不错。否则,这个新功能很棒!我已经创建了一个 Google Gears 上传,效果很好,但不知何故,Google Gears 的更新对于新版本来说很慢。将文件分成块发送也是一个好主意,以及发布服务器端脚本(好吧,我现在很高兴,但可能还会有其他人)。

    2010 年 1 月 29 日 下午 08:22

  16. Ryan

    将文件分成块目前正在 webapps 邮件列表中讨论,因此 mozilla 暂时没有将此功能添加到 ff3.6 中。他们将有一个可以切成更小块的 blob,显然是从 google gears 获得了这些想法。 http://www.w3.org/TR/FileAPI/#dfn-Blob

    2010 年 1 月 29 日 下午 18:33

  17. Eric Jain

    太可惜了,你不能将拖放的文件设置到一个

    2010 年 2 月 4 日 下午 17:48

  18. Craig Morrison

    我在以下位置有我的看法:

    http://www.sillywindows.com/

    很明显要点击哪个链接才能获取它。

    2010 年 3 月 1 日 下午 21:55

  19. [...] 在您读取完关于文件(s)的必要数据后,您可以使用 FileReader 对象获取二进制数据,然后使用 XMLHTTPRequest 对象将其发布到 web 服务器。有关此方面的更多信息,请参见 FileReder 文档和 Paul Rouget 的文章“使用拖放、FileAPI 和 XMLHttpRequest 进行交互式文件上传”。[...]

    2010 年 4 月 22 日 上午 08:32

  20. Daniel Kirsch

    如何同时发送普通表单数据和文件,并使用进度指示器?是否需要先使用 xhr.sendAsBinary 上传文件,然后在成功后使用第二个请求发送其他表单数据?
    在 FF4 中,我可以使用 FormData,但我也想支持 FF 3.6。

    2010 年 9 月 21 日 下午 15:02

  21. Helio Bentes

    如何获取图像并在我的服务器上使用 PHP 保存它?

    2010 年 12 月 29 日 上午 06:26

    1. hash

      您可以编写 multipart 请求,通过 sendAsBinary 方法发送它,并在服务器端像往常一样处理文件上传。

      2010 年 12 月 29 日 下午 14:56

  22. kris

    我今天一直在玩 fileAPI - 有些东西让我感到困惑,这可能是一个新手问题,或者是一个需要多喝咖啡的问题,但我还是想问一下

    在这个例子中,upload.php 长什么样子?
    我的服务器端代码长什么样子才能访问我收到的数据?

    这些数据都是 $_POST 中的原始数据吗? - 我猜我可以自己试试,看看是不是这样... 等等... 对,$_POST 是一个空数组,$_FILES 也是。

    请帮忙!(我看到“Hello Bentes”和“Preben Borch”问了同样的问题,但还没有回复... “Ryan”你的回复很棒,但还是想知道是否有可能使用不需创建自定义 JS 上传的服务器端程序)

    2011 年 2 月 24 日 上午 00:24

  23. Preben Borch

    kris,你需要在 PHP 中打开 stdin 并将其转储到一个文件中。

    要打开指向 stdin 的文件指针,请参考以下示例:

    https://php.ac.cn/manual/en/features.commandline.io-streams.php

    2011 年 2 月 28 日 上午 10:24

本文的评论已关闭。