XHR 进度和丰富的文件上传反馈

这个演示是由 Olli Pettay(smaug)在 Austin King 的帮助下完成的。

当今网络上一个常见的局限性是缺乏用于 Web 应用程序的丰富的文件上传小部件。许多网站使用 Flash 或桌面辅助应用程序来改善文件上传体验。

Firefox 3.5 弥合了这些差距之一,允许构建更好的进度指示器。许多开发人员没有意识到他们可以使用 Firefox 的 File 对象(nsIDOMFile)和 XMLHttpRequest 一起完成文件上传。这个演示将展示一个上传小部件,它提供了用户期望的那种丰富的进度反馈,以及快速简便的多文件同时上传。

进度指示器

向用户提供应用程序正在为他们努力工作的反馈信息总是好的,特别是当当前操作预计何时完成时。主要有两种类型的进度反馈:

  • 不确定进度 - 某些活动刚刚发生
  • 确定性进度 - 我完成了 40%,我完成了 42% …… 等等

确定性进度?演示

我们创建了一个简单的文件上传/下载页面,演示了进度条

演示托管在 mozilla.pettay.fi,需要 Firefox 3.5 beta4 或更高版本。它演示了如何执行多文件同时上传,无需提交表单或离开当前页面。对于每个文件上传/下载,我们显示当前速度、完成百分比和已传输字节数。我们将回顾代码中的一些关键片段,这些片段在上面的屏幕截图中使用。请点击 演示 并查看源代码以获取完整的代码示例。

该页面包含两个 HTML 输入,一个 type="file" 和一个 type="button"。表单实际上从未提交,而是向按钮添加了一个 onclick 处理程序

<input type="file" id="file">
<input type="button"
          onclick="startXHR();"
          value="Upload file using XHR">

在 startXHR 函数中,我们创建了一个 XMLHttpRequest 并向 XHR 请求添加了一个事件处理程序,以监听新的“进度”事件。使用此 ProgressEvent 的 lengthComputable 属性,我们将知道我们是否正在处理不确定进度或确定性进度。该对象还提供了已加载和总字节数。

var xhr = new XMLHttpRequest();

xhr.onprogress = function(evt) {
if (evt.lengthComputable) {
    evt.target.curLoad = evt.loaded;
    evt.target.log.parentNode.parentNode.previousSibling.textContent =
        Number(evt.loaded/k).toFixed() + "/"+ Number(evt.total/k).toFixed() + "kB";
}
if (evt.lengthComputable) {
    var loaded = (evt.loaded / evt.total);
    if (loaded < 1) {
        var newW = loaded * width;
        if (newW < 10) newW = 10;
            evt.target.log.style.width = newW + "px";
        }
    }
};

现在我们需要一些要发送的数据。我们通过 ID 直接从输入中获取文件内容

var files = document.getElementById("file").files;
if (files) {
   var file = files.item(0);
   xhr.sendAsBinary(file.getAsBinary());

最后一步是启动请求

xhr.open("POST", "cgi-bin/posthandler.pl");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
xhr.sendAsBinary(file.getAsBinary());

这些方法也适用于 xhr.upload.onprogress

注意在 sendAsBinary 方法上使用 XMLHttpRequest 对象和 getAsBinary 方法在 File 对象上。从 Firefox 3 开始,您可以在不提交表单的情况下获取客户端上文件的內容。这对超越传统文件输入和表单提交的局限性非常有用。它也是即将推出的 W3C 标准 的一部分,用于文件上传。

nsIDOMFile 提供的另一个相关方法是 getAsText 方法,它返回一个 DOMString,适合切片、切块和显示。

这是一个示例用法,演示中没有使用

file.getAsText("utf8");

这就是代码的要点。查看 演示及其源代码

用户界面中的反馈

向用户公开系统反馈可以提高感知性能。我们
无法始终确定某些操作需要多长时间,因此至少我们
可以显示不确定进度。

在文件上传和文件下载期间(假设服务器提供了 Content-Length),我们确实知道总字节数。Firefox 3.5 的进度事件支持添加了一个进度事件,以便我们可以显示实际的上传/下载进度。

传统上,从 XMLHttpRequests 获取确定性进度很困难。理论上,您可以为其提供回调并观察状态码更新和文本消息更新,但在实践中,它们并不十分有用。在过去,如果确定性进度计很重要,您将不得不发出第二个 XHR 请求来轮询进度。

引入进度事件

W3C 已经发布了 进度事件 1.0 的工作草案,我们将其包含在 Firefox 3.5 中。Firefox 添加了一个关键的新 DOM ProgressEvent progress 事件,以及 loadstart 事件。其他现有的事件包括:error、abort 和 load。

这些相同的事件也适用于上传和下载。进度事件提供了以下属性

  • lengthComputable - true 或 false,请求的大小是否已知?
  • loaded - 到目前为止已接收的字节数
  • total - 整个请求的预期字节数

契约

当您查看这些进度事件的属性时,会应用某些规则,您可以依赖这些规则。它们是

  • 当 lengthComputable 为 false 时,total 属性将为 0。
  • loadstart 事件始终只发出一次信号。
  • 进度事件在 loadstart 之后被触发零次或多次。

就是这样。使用 Firefox 3.5 的强大功能,开始改进文件上传吧。

关于 Austin King

位于西雅图的非教条主义艺术家/程序员型人类。应用程序工程团队的无赖 Web 开发者。拼写检查留给本周吧。

更多 Austin King 的文章…


38 条评论

  1. Dan

    太棒了。FileUpload 标准是否允许这种无需脚本即可进行表单提交的操作?

    2009 年 6 月 18 日 下午 07:21

  2. ozten

    不,我不这么认为。FileUpload 规范标准化了文件选择机制和 API,使其可用于非 Web 浏览器环境。它仍然需要使用脚本。

    2009 年 6 月 18 日 下午 07:46

  3. Matthew Wilson

    这里有什么安全限制?您听起来像是说网站 JavaScript 可以读取本地文件系统上的任何文件。我知道事实并非如此,那么您能概述一下有哪些限制吗?

    2009 年 6 月 18 日 上午 09:34

  4. ozten

    在用户使用文件输入选择文件之前,您无法读取文件,因此它是由用户发起的并由用户控制。

    2009 年 6 月 18 日 上午 09:41

  5. xaron

    是否也支持多文件选择?
    或者您必须一个一个地选择文件?

    2009 年 6 月 18 日 上午 09:58

  6. Ken Arnold

    看起来您仍然只能在选择框中一次选择一个文件(使用 Ubuntu Jaunty 中的 Fx3.5 beta)。但是 API 似乎支持让单个文件输入选择多个文件。这在 3.5 正式版中将得到支持吗?

    2009 年 6 月 18 日 下午 11:42

  7. scribu

    这很好,但还缺少一些东西:一次选择多个文件的功能。

    2009 年 6 月 18 日 下午 13:56

  8. Christopher Blizzard

    @scribu – 是的,我们还没有添加多文件上传。我认为我们将在下一个版本中尝试这样做。

    2009 年 6 月 18 日 下午 18:53

  9. @F1LT3R

    太酷了!我一直希望这成为可能,大约 5 年了。演示很酷。

    2009 年 6 月 18 日 下午 19:19

  10. […] XHR 进度和更丰富的文件上传反馈 - Scriptorama 之前就曾写过关于文件上传进度条的文章:file upload progressbars。Firefox 的团队现在提供了一种新的方法。[…]

    2009 年 6 月 18 日 下午 10:48

  11. […] 在 hacks.mozilla.org 上查看 XHR 进度和更丰富的文件上传反馈 如今,Web 应用程序的一个常见限制是缺乏丰富文件上传小部件。许多网站使用 Flash 或桌面辅助应用程序来改善文件上传体验。[…]

    2009 年 6 月 19 日 上午 1:14

  12. voracity

    哇,我从来不知道 3.0 版本可以读取文件内容。太棒了。我爱死 h.m.o. 了。

    2009 年 6 月 19 日 上午 4:12

  13. Gilberto Ramos

    我应该多了解一下 XHR!这看起来很棒。

    2009 年 6 月 19 日 上午 8:00

  14. Mariusz Nowak

    我认为更大的限制是缺乏多文件选择功能,而进度指示器在只上传一个文件时并没有那么有用。
    所以,请,请实现多文件选择功能。Opera 和 Safari 已经有了这个功能 ;-)

    2009 年 6 月 20 日 上午 7:14

  15. […] 文件上传的更丰富反馈 […]

    2009 年 6 月 20 日 下午 2:34

  16. schrep

    这真的很酷 - 你知道其他浏览器是怎样做的,或者将来会支持这个吗?

    2009 年 6 月 21 日 下午 12:06

  17. […] 原文地址:XHR progress and rich file upload feedback 系列地址:颠覆网络35天 […]

    2009 年 6 月 21 日 下午 11:36

  18. Christopher Blizzard

    @schrep - 我不确定。我知道很多东西都出现在规范中,WK 团队在这方面非常强大,所以我确信这个时间框架会很短。

    IE 则不同,但他们在最近的版本中已经采用了一些新的标准。我将咨询 Arun,看看他是否有任何反馈。

    2009 年 6 月 22 日 上午 9:11

  19. Arun Ranganathan

    @schrep: 像 *.getAsBinary() 这样的同步方法将不会得到其他供应商的支持。我正在努力开发 异步文件 API(带回调)。但是,其他浏览器也会支持 ProgressEvent。

    2009 年 6 月 22 日 下午 12:13

  20. […] 改进了开发人员对浏览器原生文件上传的控制,这是我在 2007 年愿望清单上的内容,将在即将发布的 Firefox 中提供。[…]

    2009 年 6 月 27 日 上午 6:26

  21. […] 在不使用 Flash 或大量 […] 的情况下创建上传进度条。[…]

    2009 年 7 月 1 日 下午 5:26

  22. Mikko Ohtamaa

    是否可以恢复上传?当将大型(视频)文件传输到服务器时,这是一个问题。

    2009 年 7 月 6 日 上午 3:15

  23. Neale

    很棒的示例,但我卡在一个问题上。在美妙的基于 Firefox 的 Web 应用程序世界中,我有一个要求,我需要使用 XHR POST 一个 zip 文件,然后对其进行处理,从而返回一个经过处理的 ZIP 文件。

    我应该能够轻松地获取异步结果的句柄,并且能够在新窗口中打开它,或者,就像在我的情况下一样,获得下载提示。

    我有一个选项是将资源缓存到服务器上,并返回 201 + Location: 用于缓存的 zip,但这并不是理想的。我宁愿直接传输新的 zip,而不必访问磁盘。

    有什么线索可以告诉我如何做到这一点吗?

    2009 年 7 月 13 日 上午 7:41

  24. […] 原文地址:XHR progress and rich file upload feedback 系列地址:颠覆网络35天 […]

    2009 年 8 月 3 日 下午 9:18

  25. […] 一些人已经弄清楚并演示了如何在 Safari 4 中进行多文件上传,以及在 Firefox 3.5 中使用的类似版本,尽管你无法选择多个文件,但你可以上传多个 […]

    2009 年 8 月 29 日 下午 9:41

  26. John

    有人如何将文本字段数据与文件上传一起发布到同一个目标页面?你能提供接收页面的 PHP 代码吗?

    2009 年 9 月 9 日 下午 1:14

  27. wirtsi

    我写了一篇关于这篇文章的后续文章……其中包括一些关于如何本地存储所有表单数据并在稍后提交所有数据的 JavaScript 示例代码

    http://blog.mykita.com/2009/10/enqueue-form-uploads-for-better-usability/

    2009 年 10 月 7 日 下午 11:04

  28. […] […]

    2009 年 11 月 11 日 上午 7:46

  29. Frankenst1

    在当前的 firebug-1.4.5.xpi 或 firebug-1.5X.0b5.xpi 中不起作用,因为 Firebug 会阻止 XHR 触发 onProgress 事件。

    2009 年 11 月 29 日 上午 9:39

  30. Shiv Kumar

    如何将文件与表单字段(multipart/form-data)一起上传?

    文件字节实际上是怎样发送到服务器端的?

    是否计划支持对 input 元素(type=”file”)的 onprogress 事件,以便人们只需在事件委托中编写代码,而不需要其他一切。

    目前,似乎只能上传一次文件。之后必须关闭浏览器并重新开始。

    2010 年 2 月 8 日 上午 11:31

  31. Shiv Kumar

    对于任何可能跟我一样有相同问题的人。

    你会使用
    fd.append(“uploadFile”, document.getElementById(‘fileField’).files[0]);

    其中 fielField 恰好是页面上的 <input type=”file” id=”fileField” > 元素。

    Files[0] 显然只会给你第一个文件。

    2010 年 9 月 21 日 下午 10:22

  32. Markus

    很棒的教程。我刚开始学习,所以有人可以告诉我服务器端的 PHP 代码应该怎样才能处理多文件上传吗?谢谢。

    2010 年 10 月 25 日 上午 5:32

  33. Anil Namde

    我对上传和下载进度感到困惑?从代码来看
    initXHREventTarget(xhr.upload, container);
    initXHREventTarget(xhr, container);
    两者都使用进度事件。但是上传先进行,然后是下载。
    有人可以解释一下这种行为的差异吗?

    2011 年 1 月 20 日 上午 7:04

    1. Shiv Kumar

      Anil,

      你到底哪里困惑了?有两个对象,每个对象都被分配了一个事件处理程序,用于处理它们的每个事件。它们恰好被分配给了相同的方法。

      演示页面先上传一个文件,然后下载一个文件。它不像同时上传和下载文件。不过,事件处理程序负责显示视觉进度。

      我在我的网站上有一个更简单的示例
      Html5 文件上传带进度,你可以去看看。

      2011 年 1 月 21 日 上午 3:54

  34. Anil Namde

    谢谢,简单的解释很有帮助。这是一个不错的开始 HTML 5 上传的演示。

    2011 年 1 月 25 日 上午 7:34

  35. Mike S

    你能推荐一些关于怎样显示进度条和文本消息的资料,这些消息显示了在幻灯片页面中,已经完全下载了多少张缩略图,以及总共有多少张缩略图?例如,下载文件 13/83,进度条显示 (12/83*100) 14%?

    2011 年 7 月 28 日 下午 8:05

  36. Stepan Suvorov

    Number(evt.loaded/k).toFixed() + “/”+ Number(evt.total/k).toFixed() + “kB”;

    除以“k” - 这里“k”变量是什么?

    2012 年 7 月 30 日 上午 3:56

  37. Stepan Suvorov

    而且演示在我的浏览器(FF14)中无法运行。

    2012 年 7 月 30 日 上午 4:55

本文的评论已关闭。