Firefox 3.6 中的 W3C FileAPI

通常,Web 应用程序会提示用户选择一个文件,通常是为了将其上传到服务器。除非 Web 应用程序使用插件,否则文件选择将通过 HTML 输入元素完成,例如 <input type="file"/>。Firefox 3.6 现在支持大部分 W3C File API,它指定了将选定文件异步读取到内存中的能力,并在 Web 应用程序中对文件数据执行操作(例如,在上传之前显示图像的缩略图预览,或查找 MP3 文件中的 ID3 标签,或查找 JPEG 文件中的 EXIF 数据,所有这些都在客户端进行)。这是一个新的 API,它取代了 Firefox 3 中引入的文件 API。

需要注意的是,即使在 W3C File API 草案出现之前(该草案直到 2009 年 11 月才成为工作草案),Firefox 3 及更高版本也提供 将文件同步读取到内存中的能力,但该功能应被认为是已弃用的,因为它将被 Firefox 3.6 中新实现的 异步 File API 取代。已弃用的 API 允许您同步访问文件

// After obtaining a handle to a file
// access the file data
var dataURL = file.getAsDataURL();
img.src = dataURL;

虽然 Firefox 3.6 将继续支持上述代码用法,但它应该被认为是已弃用的,因为它在主线程上同步读取文件。对于大型文件,这会导致阻塞读取结果,这是不可取的。此外,文件对象本身提供了一个方法来从中读取,而不是拥有一个单独的阅读器对象。这些考虑因素决定了 Firefox 3.6 中新的 File API(以及 规范)的技术方向。本文的其余部分将介绍新引入的 File API。

访问文件选择

Firefox 3.6 支持在输入元素上进行多个文件选择,并使用 FileList 接口返回所有选定的文件。以前版本的 Firefox 仅支持使用输入元素选择一个文件。此外,FileList 接口也作为 DataTransfer 接口的属性暴露给 HTML5 拖放 API。用户也可以将多个文件拖放到网页内的拖放目标上。

以下 HTML 代码会生成标准的文件选择器,您可以使用它选择多个文件

请注意,如果您不使用 multiple 属性,则只能启用单个文件选择。

您可以通过遍历 FileList 来处理通过文件选择器(使用 input 元素)或通过 DataTransfer 对象获得的所有选定文件

var files = document.getElementById("inputFiles").files;

// or, for a drag event e:
// var dt = e.dataTransfer; var files = dt.files

for (var i = 0; i < files.length; i++) {
  var file = files[i];
  handleFile(file);

}

文件的属性

FileList 中获取对单独选定文件的引用后,您将获得一个 File 对象,它具有 nametypesize 属性。继续上面的代码片段

function handleFile(file) {
    // RegExp for JPEG mime type
    var imageType = /image/jpeg/;

    // Check if match
    if (!file.type.match(imageType)) {
        return false;
    }
   // Check if the picture exceeds set limit
   if(file.size > maxSize) {
      alert("Choose a smaller photo!");
      return false;
   }
  // Add file name to page
  var picData = document.createTextNode(file.name);
  dataGrid.appendChild(picData);
  return true;
}

size 属性是以字节为单位的文件大小。name 属性是文件的名称,不含路径信息。type 属性是一个 ASCII 编码的字符串,以小写字母表示文件的媒体类型,表示为 RFC2046 MIME 类型type 属性特别有用,因为它可以用于嗅探文件类型,如上面的示例所示,其中脚本确定该文件是否为 JPEG 文件。如果 Firefox 3.6 无法确定文件的 type,它将返回空字符串。

读取文件

Firefox 3.6 及更高版本支持 FileReader 对象,该对象使用事件回调来标记进度,将文件数据异步读取到内存中。该对象以标准方式实例化

var binaryReader = new FileReader();

事件处理程序属性用于处理文件读取操作的 result。对于非常大的文件,可以观察文件被读取到内存中的进度事件(使用 onprogress 事件处理程序属性来设置事件处理程序函数)。这在以下情况下非常有用:所讨论的驱动器可能不是本地硬件,或者所讨论的文件特别大。

FileReader 对象支持三种方法将文件读取到内存中。每种方法都允许以不同的格式对文件数据进行编程访问,但在实践中,应该只对给定的 FileReader 对象调用一种读取方法

  • filereader.readAsBinaryString(file); 将异步返回一个二进制字符串,其中每个字节都由 [0..255] 范围内的整数表示。这对于对文件数据的二进制操作很有用,例如,查找 MP3 文件中的 ID3 标签,或查找 JPEG 图像中的 EXIF 数据
  • filereader.readAsText(file, encoding); 将异步返回一个字符串,该字符串的格式由编码参数(例如 encoding = "UTF-8")指定。这对于处理文本文件很有用,例如,解析 XML 文件。
  • filereader.readAsDataURL(file); 将异步返回一个 数据 URL。Firefox 3.6 允许使用大型 URL,因此此功能在 URL 可以帮助在网页中显示媒体内容时特别有用,例如,用于图像数据、视频数据或音频数据。

一个示例可以帮助将所有这些联系在一起

if (files.length > 0) {
    if (!handleFile(files[0])) {
        invalid.style.visibility="visible";
        invalid.msg = "Select a JPEG Image";
     }
}

var binaryReader = new FileReader();
binaryReader.onload = function(){
   var exif = findEXIFInJPG(binaryReader.result);
   if (!exif) {
      // ...set up conditions for lack of data
   }
   else {
    // ...write out exif data
   }

binaryReader.onprogress = updateProgress;
binaryReader.onerror = errorHandler;

binaryReader.readAsBinaryString(file);

function updateProgress(evt){
   // use lengthComputable, loaded, and total on ProgressEvent
   if (evt.lengthComputable) {
          var loaded = (evt.loaded / evt.total);
          if (loaded < 1) {
            // update progress meter
            progMeter.style.width = (loaded * 200) + "px";
          }
   }
}

function errorHandler(evt) {
  if(evt.target.error.code == evt.target.error.NOT_FOUND_ERR) {
   alert("File Not Found!");
  }
}

为了处理二进制数据,使用字符串上公开的 charCodeAt 函数将特别有用。例如,以下实用程序

function getByteAt(file, idx) {
    return file.charCodeAt(idx);
}

允许提取给定索引处字符的 Unicode 值。

可以在 Paul Rouget 关于 File API 的精彩演示 中找到在 Firefox 3.6 中类似代码的实际示例,其中包括使用 readAsDataURL 方法呈现图像以及对 JPEG 进行二进制分析以进行 EXIF 检测(使用 readAsBinaryString 方法)。

关于规范的一句话

存在 File API 的 W3C 公共工作草案,这预示着其他浏览器将在不久的将来实现它。Firefox 3.6 的实现相当完整,但缺少规范中提到的某些技术。值得注意的是,urn 特性在 File 对象中尚未实现,使用 slice 方法提取文件的字节范围的能力也没有实现。尚未在 Worker 线程 中实现同步读取文件的方法。这些功能将在 Firefox 的未来版本中提供。

参考文献

关于 Arun Ranganathan

Arun Ranganathan 的更多文章...


38 条评论

  1. […] 关于 « Firefox 3.6 中的 W3C FileAPI […]

    2009 年 12 月 9 日 下午 3:58

  2. Ivan Enderlin

    非常非常非常棒的工作,伙计们 :-)。

    2009 年 12 月 9 日 下午 4:22

  3. mwilcox

    太棒了,伙计们。我期待在 Dojo FileUploader 中实现它。有一个问题——我可以将它设置为与我的其他表单元素和按钮的外观一致吗,还是安全方面受到限制(迫使我们继续创建 hack 来解决这个问题)?

    2009 年 12 月 9 日 下午 5:12

  4. Aryeh Gregor

    multiple=”true” 无效。HTML5 将 multiple 定义为布尔属性,[1] 所以您需要使用 multiple=”multiple”,或者 multiple=””,或者(在 HTML 序列化中,而不是 XHTML)只使用 multiple,不使用 = 或引号。[2]

    [1] http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#attr-input-multiple

    [2] http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#boolean-attribute

    2009 年 12 月 9 日 下午 5:41

  5. rdza

    难以置信的工作——规范在一个月内发布,实现紧随其后,真是太棒了!_而且_异步是在核心代码中的,所以即使是害怕回调的人也能从自身中解脱出来。
    现在我们需要一个 w3c fileapi js->as3 shim,通过 flash 10 filereference() 为 IE 提供支持。

    2009 年 12 月 9 日 下午 7:46

  6. […] hacks 博客最近发布了一些关于 File API 的精彩信息,以及一个从 […] 中提取 EXIF 数据的优秀演示。

    2009 年 12 月 9 日 下午 11:54

  7. Brian King

    太棒了。Jetpack 中有这个功能吗?

    2009 年 12 月 10 日 上午 1:46

  8. […] W3C FileAPI 在 Firefox 3.6 中,位于 hacks.mozilla.org FileAPI[API] […]

    2009 年 12 月 10 日 上午 8:05

  9. thinsoldier

    “(例如,要显示图像的缩略图预览,…”

    是否可以将原始图像和本地生成的缩略图都上传到服务器,而不是在服务器端处理原始图像?

    我的很多客户每天都会上传大量超大的数码照片。他们讨厌等待 2MB-8MB 的文件上传,我也讨厌服务器在从原始图像生成小型、中型、大型和 800×600 版本时出现的明显减速。

    2009 年 12 月 10 日 上午 9:59

  10. Ms2ger

    请使用 <input id=”inputFiles” type=”file” multiple=”” /> 或 <input id=”inputFiles” type=”file” multiple=”multiple” />。<input id=”inputFiles” type=”file” multiple=”true” /> 是无效的。另外,请告诉我,当我评论你的博客时,我的尖括号会发生什么情况。

    2009 年 12 月 10 日 上午 10:43

  11. […] 3.6 也支持 FileAPI。这使您可以在将文件发送到服务器之前,在客户端侧进行额外的处理。[…]

    2009 年 12 月 10 日 下午 2:20

  12. Arun Ranganathan

    @thinsoldier,如果你使用 DataURL 生成缩略图,它基本上是原始图像的 Base64 编码。当然,可以上传它,但这并不是你理想情况下想要的缩略图。也就是说,*可能*可以使用 Canvas 操作图像以获得更低分辨率的缩略图。这绝对值得研究。

    2009 年 12 月 10 日 下午 5:04

  13. Arun Ranganathan

    另外,@AryehGregor 指出,我在本文的原始版本中错误地使用了 multiple 属性。你不应该说 multiple=”true”,而应该简单地说 multiple=”” 或者直接说 multiple

    2009 年 12 月 10 日 下午 5:13

  14. Michael Newton

    实际上,像这样的属性应该写成 multiple=”multiple” 或者,正如你所说,直接写 multiple。浏览器能够处理其他变体只是历史遗留问题。

    http://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.3.4.2

    2009 年 12 月 10 日 下午 9:53

  15. STolsma

    这很好用,但有时 EXIF 数据无法读取或无法完全读取我自己的图片(以及我朋友的许多图片)。

    看起来可能是 EXIF 解析器不完全兼容,我的图片不符合 EXIF 标准,或者其他一些问题导致了这种情况。

    我会尝试找到问题,但我很好奇是否有人也遇到了同样的问题。

    2009 年 12 月 11 日 上午 10:27

  16. schnalle

    这意味着用户选择的这些文件现在可能在表单提交之前就可见于服务器了!?

    我实际上对此有点不安,但…无所谓。以后在文件上传对话框中要注意不要选择错误的文件,否则它可能会被互联网看到!

    2009 年 12 月 14 日 上午 6:31

  17. Aryeh Gregor

    @Michael Newton:multiple、multiple=”” 和 multiple=”multiple” 在 HTML5 中都是有效的。multiple=”” 是 HTML5 中新允许的,在 HTML 4 中是被禁止的。允许它的理由是它更短,而且所有浏览器都支持它。

    HTML5 还要求浏览器将 multiple=”anything” 与 multiple=”multiple” 视为相同。所以 multiple=”true” 肯定会起作用,但它是非法的。

    参考

    http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#boolean-attributes

    在讨论 HTML5 特性时引用 HTML 4 规范有点可笑。:)

    2009 年 12 月 22 日 下午 1:55

  18. webwurst

    很棒的东西!
    如果我启用了“Jetpack 0.7”插件,它就无法正常工作。我花了一些时间才弄明白。:)

    2010 年 1 月 10 日 上午 8:38

  19. Marcus Rodriguez

    很棒的功能,但没有提到读取文件夹/目录。我们有一些应用程序,在这些应用程序中,能够抓取整个文件夹并对其进行处理非常有用,甚至可能包括子文件夹。这可能吗?

    2010 年 1 月 14 日 上午 4:21

  20. […] 支持一些“开放”的音频和视频格式,以推动向 HTML5 的大规模过渡,Firefox 现在支持所有规范(包括文件访问)。[…]

    2010 年 1 月 22 日 上午 12:15

  21. demian

    是否可以读取任何文件,还是只能读取用户选择的那些文件?我可以猜测一个系统文件并窃取其内容吗?没有人阻止我更改输入值…

    2010 年 2 月 12 日 上午 5:15

    1. schnalle

      无法更改文件上传元素的输入值。

      2010 年 2 月 12 日 下午 2:00

  22. demian

    好吧…我甚至没有尝试过…
    是否可以使用同一个 FileReader 对象读取多个文件?

    2010 年 2 月 13 日 下午 2:29

  23. Greg

    我刚刚发布了一个工具 (http://bitdu.mp),它利用 File API 在浏览器内进行文件加密(使用 jsCrypto 库),然后将加密数据存储到 drop.io。当文件下载时,解密在浏览器内完成,然后使用数据 URI 将解密后的数据发送回客户端。

    2010 年 2 月 24 日 下午 12:36

  24. […] 我曾经用来激发讨论。我谈到了 HTML5(包括 WebApps API,例如 File API 和 Orientation Events),CSS3 的 @font-face 属性,并讨论了它对 […] 的潜力。

    2010 年 3 月 11 日 上午 3:58

  25. Arnold

    是否有计划实现 FileApi 中的“slice”函数?

    2010 年 4 月 17 日 上午 2:19

  26. […] 3.6 还支持 File API。这个 API 允许你在将文件发送到服务器之前,在客户端侧进行 […]

    2010 年 5 月 17 日 上午 4:31

  27. […] 来源:https://hacks.mozilla.ac.cn/2009/12/w3c-fileapi-in-firefox-3-6/ […]

    2010 年 6 月 20 日 下午 8:47

  28. Michael Ressler

    我喜欢这个发展方向,尤其是在 Firefox 4 中引入了 FormData 之后。我正在尝试异步发送大型视频文件(大约 500MB 的数据),FileReader 无法胜任这项任务。

    当我使用 XMLHttpRequest 和 .send(file) 时,在服务器甚至注册 POST 请求之前,会有一段很长的 UI 延迟(我根本无法与 Firefox 交互)。对于更大的文件,服务器套接字会在等待读取数据时超时,而 Firefox 仍然很忙,不允许多用户交互。

    我尝试了发送二进制数据 (https://mdn.org.cn/En/XMLHttpRequest/Using_XMLHttpRequest#Sending_binary_data) 示例,其中使用了 nsIFileInputStream 代码,但给出的示例不起作用,我还没有找到实例化 nsIFileInputStream 并使用它的正确方法。任何帮助都将不胜感激。

    是否有干净地处理包含几百兆字节数据的 POST 请求的预期方法?

    谁能给我指点一下方向?感谢你的帮助!

    2010 年 8 月 31 日 下午 9:26

    1. Christopher Blizzard

      是的,我认为在 Firefox 4 发布之后,我们将添加对文件拼接的支持,这样你就可以进行部分上传。

      发送二进制数据示例发生了什么情况?它应该可以正常工作。

      2010 年 9 月 9 日 上午 8:52

      1. Michael Ressler

        澄清一下,我并不是在询问部分文件上传。

        我希望能够通过 JavaScript 上传一个 700MB 文件的整个内容,而不会让用户界面卡死或套接字超时。我认为在 .send(file) 方法中将 File 对象传递给 XMLHttpRequest 应该会在后台完成正确的事情(不会在构建整个请求时挂起 UI)。

        我在网上搜索了一下,看起来 nsIFileInputStream 是只有在我经过一些棘手的签名代码操作后才能访问的。我认为这也有一定的前景,但我认为在这种情况下没有必要对我的 JS 进行签名。我试图发送用户浏览或拖放到我页面上的文件的内容。我应该能够将该文件的整个内容发送回我的服务器,无论文件大小如何。

        Firefox 是否在提交请求之前,在 JavaScript/UI 线程中构建了整个 HTML POST?这就是它卡住的原因吗?是否有任何方法可以在 UI 线程之外构建 POST 消息?

        感谢任何指点!非常感谢。

        2010 年 9 月 9 日 下午 3:29

  29. Michael Ressler

    我手中已经没有代码可以用来测试它了,但我记得,JavaScript 错误是,我没有权限(或类似的东西)访问 Components.classes。这意味着我无法实例化 nsIFileInputStream。这意味着我是一个不幸的开发者,我坦白地说,我现在正在考虑使用 Flash 上传器。我感觉很肮脏。

    救命!

    2010 年 9 月 9 日 上午 9:14

  30. Michael Ressler

    我得到的错误是(它显示在 Firebug 中)

    拒绝 {http://localhost:8080} 获取属性 XPCComponents.classes 的权限
    var stream = Components.classes[“@mozilla.org/network/file-input-stream;1”]

    2010 年 9 月 9 日 上午 10:00

    1. Christopher Blizzard

      你不再有代码可以重现这个问题了吗?这将使找到它…变得困难。

      2010 年 9 月 9 日 上午 10:13

      1. Michael Ressler

        我有代码。它没有很好地打包,但根据错误消息,它似乎在“Components.classes”查找时失败了。Components.class 周围是否存在一些有趣的权限代码?即使我在 Firebug 中只是尝试检查 Components.class,它也会失败。

        2010 年 9 月 9 日 上午 10:17

    2. Michael Ressler

      显然,我不能在这里的评论中使用尖括号,所以我会用大括号代替

      拒绝 {http://localhost:8080} 获取属性 XPCComponents.classes 的权限
      var stream = Components.classes[“@mozilla.org/network/file-input-stream;1”]

      2010 年 9 月 9 日 上午 10:15

  31. Miguel Puig

    非常感谢!
    非常棒的工作!

    2011 年 6 月 29 日 上午 9:34

  32. […] 作为二进制数组,因此你首先需要使用 File API 将文件的内容读取为二进制字符串。因为拖放和输入标签都允许你一次处理多个文件,[…]

    2012 年 4 月 13 日 上午 6:57

本文的评论已关闭。