• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

HTML5 File 与 FileReader 教程

JAVA web前端中文站 2年前 (2017-07-05) 807次浏览 已收录 0个评论

HTML5 新增了很多特性,其中 File API 是非常重要的部分。在肉大师中,我大量使用了 HTML5 的文件 API,这样一来可以给予用户近乎桌面软件的体验,二来还能减少服务器和带宽的消耗。今天终于把最后几个问题解决了,在这里总结下 HTML5 File API 的使用。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

File 用途

在 W3C 页面上,列出了 File API 可能用到的场合(以下为意译):

  1. 断点续传
    上传时,先把目标文件复制到本地沙箱,然后分解逐块上传
    浏览器崩溃或者网络中断也没关系,因为恢复后可以续传
  2. 需要大量媒体素材的应用,比如视频游戏
    下载压缩包,在本地解压,就能恢复之前目录结构
    跨平台
    通过渐进式下载,进入新关卡或者开启新功能均无需等待,因为玩的时候所需素材已经通过后台下载完成了
    从本地缓存中直接读取素材,速度飞快
    二进制文件也不在话下
    使用压缩包可以大大减轻带宽和服务器消耗,也避免了频繁下载碎片文件带来的检索问题
  3. 离线图片/音频编辑器通
    不怕频繁读写大量数据
    只想重写文件的某些部分也能做到(比如修改 ID3 或者 EXIF 信息)
    创建目录组织项目后用起来舒服多了
    编辑完的文件还能被 iTunes、Picasa 之类的本地应用访问
  4. 离线视频播放器
    下载超过 1G 的大文件,将来想看再看
    可以在不同时间点间来回跳转播放
    能够给 Video 标签提供 URL
    即便片子还没下完,也能把下载到的部分先睹为快
    还能任意截取一段视频交给 Video 标签播放
  5. 离线邮件客户端
    下载保存附件到本地自不必说
    断网的情况下,可以缓存用户要上传的附件,以后再上传
    需要时可以列出缓存里的附件,通过缩略图显示,预览后上传
    能像正常服务器那样触发标准的下载动作
    不仅能使用 XHR 一次性上传全部内容,还可以把邮件和附件拆解成小块依次发送

听起来都是些令人振奋的功能,实际用起来还是要踩点坑。下面就把我的经验分享一下。

FileReader

很多浏览器都实现了 FileReader,关于它的教程和文章很多,而且常与同样被 HTML5 引入的 DND(Drag & Drop)API 连用,以支持“上传图片文件前先预览”的功能。比如:

  1. NATIVE HTML5 DRAG AND DROP
  2. Reading local files in JavaScript
  3. HTML5 Drag and Drop Upload and File API Tutorial

所以这个主题我就不再多着笔墨去写了,值得注意的有以下几点:

  • 只有 <input type="file" /> 和拖拽文件后可以获得 FileList,无法通过 URL 直接读取文件(后面要说到的本地文件可以)
  • FileList 形似数组,可以用脚标取元素,而且有 length 属性,但它本身并不是数组,不支持 concat 和 slice 等方法,要操作只能遍历,或者用 Array.prototype.slice.call()

本地文件

这里必须解释一下“本地文件”的概念。本地文件是 HTML5 本地存储的一部分(不特指那个 localStorage),本地存储现在分为 4 个等级:

  • Cookie,最古老,100K 限制,通用
  • localStorage,新增,各浏览器的容量限制不等,采用“键-值”对应的方式存储,按域划分沙箱,不能跨域读写数据
  • 数据库,新增,容量限制不等,标准不一
  • 本地文件,新增,目前只有 Chrome 12+支持;全体域的临时文件共享 1G 空间,每个域需单独请求持久化存储空间;不能跨域操作,但同域下本地文件和远程文件之间不算跨域

Windows 的存储路径为:C:/Users/用户名/AppData/Local/Google/Chrome/User Data/Default/File System;
Mac 的存储路径为:~/Library/Application Support/Google/Chrome/Default/File System/。
不能像对普通文件那样直接操作它们的真身,只能用 JavaScrip。

这里的“本地文件”并不是我们通常意义上说的文件,没有直接存放在操作系统的文件体系中,直接搜索文件名是找不到的。可以用“filesystem:http://domain/temporary/文件名”(假设域名是 domain)来访问,比如:

 // 经本地环境下的 js 写入的文件 <img src="filesystem:file:///temporary/MG_8764.jpg" /> // 经远程环境下的 js 写入的文件 <img src="filesystem:http://blog.meathill.net/temporary/MG_8764.jpg" />

本地环境下,这个 API 同样受到限制,需要在 Chrome 启动时添加参数:–allow-file-access-from-files 方可正常使用。

后面几节的内容基本都是我从 EXPLORING THE FILESYSTEM APIS 和 File API: Directories and System 中学到的。目前关于本地文件 API 的介绍很少,应用也不多(毕竟仅限 Chrome),不过看看那些令人心动的应用场景,相信不久我们就能在更多浏览器里看到它的身影了。

接下来,我们还是边看代码边进行吧。操作本地文件前需要请求空间:

 // 判断是何种浏览器,使用不同的函数 window.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem; // type 类型,TEMPORARY 临时,所有文件共享 1G 空间;或者 PERSISTENT 永久,需单独请求 // size 容量,单位是字节 // 成功失败两个回调函数,后面仍会大量出现 window.requestFileSystem(type, size, successCallback, opt_errorCallback);

请求空间成功后,会调用成功的回调函数,并传入 FileSystem 的实例,我们可以把它存起来,以备后用。

 function fileSystemReadyHandler(fs) {   fileSystem = fs; }

错误的回调函数写一个通用的就行了,以后几乎每次都要用到:

 function errorHandler(e) {   var msg = '';   switch (e.code) {     case FileError.QUOTA_EXCEEDED_ERR:       msg = 'QUOTA_EXCEEDED_ERR&';       break;     case FileError.NOT_FOUND_ERR:       msg = 'NOT_FOUND_ERR';       break;     case FileError.SECURITY_ERR:       msg = 'SECURITY_ERR';       break;     case FileError.INVALID_MODIFICATION_ERR:       msg = 'INVALID_MODIFICATION_ERR';       break;     case FileError.INVALID_STATE_ERR:       msg = 'INVALID_STATE_ERR';       break;     default:       msg = 'Unknown Error';       break;   };   console.log('Error: ' + msg); }

请求临时空间(TEMPORARY)就这么简单;请求持久化存储空间(PERSISTENT)稍微复杂些,因为临时存储是所有应用共享 1G 空间,而持久化存储则是按照域来单独授予空间,所以后者后必须经过用户许可才行。代码方面要在 requestFileSystem 之前,先请求空间,用户许可后方可继续:

 window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024, function(grantedBytes) {   window.requestFileSystem(PERSISTENT, grantedBytes, fileSystemReadyHandler, errorHandler); }, function(e) {   console.log('Error', e); });

Chrome 会降下黄条,询问用户是否允许该域使用持久化存储,用户同意后才会请求 FileSystem。

复制文件

复制文件指把文件从操作系统的文件系统复制到本地文件的文件系统中。这个操作很简单,也很有代表性,请先看代码:

 this.clone = function (file) {   // file 即通过拖拽或者<input type="file" />选择产生的 File 实例   targetFile = file;   // create true 表示如果该文件不存在,则创建之   fileSystem.root.getFile(file.name, {create: true}, fileEntry_cloneReadyHandler, errorHandler); } function fileEntry_cloneReadyHandler(fileEntry) {   // 获取本地文件的 URL,可以赋给 img 的 src 属性,一般是 filesystem://domain://temporary 或者 persistent/路径/文件名   fileURL = fileEntry.toURL();   fileEntry.createWriter(fileWriter_cloneReadyHandler, errorHandler); } function fileWriter_cloneReadyHandler(fileWriter) {   // onwrite 的话可能文件还没写完,所以最好用 onwriteend,这点我参考的教程中没有提到   fileWriter.onwriteend = function(e) {     console.log('Write completed.');   };   fileWriter.onerror = function(e) {     console.log('Write failed: ' + e.toString());   };   fileWriter.write(targetFile);   targetFile = null; } 

几乎所有的文件型操作都包括以上三步:获取文件、创建 FileWriter、写入。与我们日常的文件操作不太一样,HTML5 的 File API 要先找到或创建文件,然后再对文件进行操作,所以文件的 URL 在 getFile 之后就已经确定下来。

getFile 有 4 个参数,分别是文件名(文件路径我这次没用到,所以这篇文章中不会提及)、文件处理策略、成功回调函数、错误回调函数。按照 w3c 规范,文件处理策略有两个参数,分别是 create 和 exclusive。前者代表如果目标文件不存在,是否创建;后者代表如果目标文件已存在,是否抛出异常,在后面“写文件”一节里会特别讲解这个参数的用法。

写文件

与复制文件不同,为了保证输出的文件可用,我们需要选择合适的文件格式和文件类型。这里我姑且假设输入的内容都是字符串,直接以文本文件来保存就可以了。至于二进制文件后面再讨论。继续看代码吧:

 this.save = function (name, content, type) {   fileName = name;   fileContent = content;   fileType = type || 'text/plain';   fileSystem.root.getFile(fileName, {create: true, exclusive: true}, fileEntry_saveReadyHandler, errorHandler); } function fileEntry_saveReadyHandler(fileEntry) {   fileURL = fileEntry.toURL();   fileEntry.createWriter(fileWriter_saveReadyHandler, errorHandler); } function errorHandler(error) {   // 前面可以照搬,后面需要增加一个处理   // 当文件已存在时,应先删除   if (error.code == FileError.INVALID_MODIFICATION_ERR) {     fileSystem.root.getFile(fileName, {create: false}, fileEntry_removeReadyHandler, errorHandler);   } } function fileWriter_saveReadyHandler(fileWriter) {   fileWriter.onwriteend = function (event) {     console.log('Write completed.');   };   fileWriter.onerror = function (error) {     console.log('Write failed: ' + error.toString());   };    var blob,       byteArray       i = len = 0;   if (/text/i.test(fileType)) {     blog = new Blob([fileContent]);   } else {     len = fileContent.length;     byteArray = new Uint8Array(len);     for (; i < len; i++) {       byteArray[i] = fileContent.charCodeAt(i) & 0xFF;     }     blob = new Blob([byteArray], {type: fileType});   }   fileWriter.write(blob);   fileContent = null; } function fileEntry_removeReadyHandler(fileEntry) {   fileEntry.remove(fileRemoveHandler, errorHandler); } function fileRemoveHandler() {   self.save(fileName, fileContent, fileType); }

可以看到,大体上还是三步:获取文件,创建 FileWriter、写入内容,不过增加了很多异常处理。这就要回到前面提到的那个 getFile,它的第二个参数“文件处理策略”,里面有个字段叫 exclusive,代表如果目标文件已经存在,是否抛出异常。因为 FileWriter 写入内容时以文件指针为标准,从 0 开始,逐字节写入,直到 fileContent 写完;当目标文件存在时,它仍会这么做,这种逐字节覆盖的方式导致如果先前的内容比后写入的内容要长,文件内容就会是新老相接的。这明显不是我们希望的结果,所以我要修改 errorHandler ,当遇到 FileError.INVALID_MODIFICATION_ERR 时就先把目标文件删除,然后再重新写入。

删除文件的操作是两步:获取文件、删除文件。完成之后,再次调用 save 方法,写入内容。

Blob 是 HTML5 中引入的新类型,代表“不可变的原始二进制数据”。它是所有文件的基础,可以是任何一种文件类型的数据。新的规范引入了 Blob 的构造函数,可以接收两个参数:内容 和 属性包。其中,内容 可以是 ArrayBuffer(后面会介绍)、其他 Blob、或者文本字符串,不过都需要用数组包裹。属性包 则是一个对象,用来指明该 Blob 的 MIME 类型和换行标记。

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:HTML5 File 与 FileReader 教程
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址