继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

「HTML5」FileAPI 文件操作实战

心谭
关注TA
已关注
手记 26
粉丝 23
获赞 81

本文来自《FileAPI 文件操作实战》
其他所有系列都放在了Github欢迎交流和Star

介绍

HTML5 为我们提供了 File API 相关规范。主要涉及 File 接口FileReader 对象

本文整理了兼容性检测、文件选择、属性读取、文件读取、进度监控、大文件分片上传以及拖拽上传等开发中常见的前端文件操作。

准备工作

首先,我们的 File 来自于<input>标签中选中的文件列表。所以,准备如下的 HTML 代码:

<input type="file" id="files" multiple />
<div id="list"></div>
<div id="images"></div>
<!-- File API相关操作写在了script.js中 -->
<script src="./script.js"></script>

检测兼容性

File 对象是特殊类型的 Blob。在 script 入口处,应该检测当前浏览器是否支持 File API:

if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
  throw new Error("当前浏览器对FileAPI的支持不完善");
}

监听文件选择

对于 type 为 file 类型的<input>标签,在选择文件的时候,会触发change事件。用户选中的文件信息也会传入回调函数的第一个参数中。

function handleFileSelect(event) {
  const { files } = event.target;
  if (!files.length) {
    console.log("没有选择文件");
    return;
  }

  console.log("选中的文件信息是:", files);
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

文件属性-File

event.target.files 是一个FileList对象,它是一个由File对象组成的列表。

每个 File 对象,保存着选中的对应文件的属性。常用的用:

  • name:文件名
  • type:文件类型
  • size:文件大小

下面,通过 type 属性,过滤掉非图片类型的文件,只展示图片类型文件的信息:

function handleFileSelect(event) {
  const { files } = event.target;
  if (!files.length) {
    console.log("没有选择文件");
    return;
  }

  const innerHTML = [];
  const reImage = /image.*/;

  for (let file of files) {
    if (!reImage.test(file.type)) {
      continue;
    }

    innerHTML.push(
      `
      <li>
        <strong>${file.name}</strong>
        (${file.type || "n/a"}) -
        ${file.size} bytes
      </li>
      `
    );
  }

  document.querySelector("#list").innerHTML = `<ul>${innerHTML.join("")}</ul>`;
}

读取文件-FileReader

还是以图片读取为例,读取并且显示所有的图片类型文件。

文件读取需要使用FileReader对象,它常用 3 个回调方法:

  • onload: 文件读取完成
  • onloadstart:文件上传开始
  • onprogress : 文件上传中触发

Image类似,在读取文件之前,需要先绑定事件处理。它读取操作有:readAsArrayBuffer、readAsDataURL、readAsBinaryString、readAsText。传入的参数就是File对象。

那么这几个方法有什么区别呢?不同的读取方式,回调事件onload接受到的event.target.result不相同。比如,readAsDataURL读取的话,result 是一个图片的 url。

下面就是读取图片文件,然后展示的一个例子:

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }

  let vm = document.createDocumentFragment(),
    re = /image.*/,
    loaded = 0, // 完成加载的图片数量
    total = 0; // 总共图片数量

  // 统计image文件数量
  for (let file of files) {
    re.test(file.type) && total++;
  }

  // onloadstart回调
  const handleLoadStart = (ev, file) =>
    console.log(`>>> Start load ${file.name}`);
  // onload回调
  const handleOnload = (ev, file) => {
    console.log(`<<< End load ${file.name}`);

    const img = document.createElement("img");
    img.height = 250;
    img.width = 250;
    img.src = ev.target.result;
    vm.appendChild(img);

    // 完成加载后,将其放入dom元素中
    if (++loaded === total) {
      document.querySelector("#images").appendChild(vm);
    }
  };

  for (let file of files) {
    if (!re.test(file.type)) {
      continue;
    }

    const reader = new FileReader();
    reader.onloadstart = ev => handleLoadStart(ev, file);
    reader.onload = ev => handleOnload(ev, file);
    // 读取文件对象
    reader.readAsDataURL(file);
  }
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

监控读取进度

在监控读取进度的时候,主要是处理 FileReader 对象上的 onprogress 事件。

下面的例子,请打开一个较大的文件来查看效果(否则一下就读取完了):

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }

  const handleLoadStart = (ev, file) =>
    console.log(`>>> Start load ${file.name}`);
  const handleProgress = (ev, file) => {
    if (!ev.lengthComputable) {
      return;
    }
    // 计算进度,并且以百分比形式展示
    const percent = Math.round((ev.loaded / ev.total) * 100);
    console.log(`<<< Loding ${file.name}, progress is ${percent}%`);
  };

  for (let file of files) {
    const reader = new FileReader();
    reader.onloadstart = ev => handleLoadStart(ev, file);
    reader.onprogress = ev => handleProgress(ev, file);
    reader.readAsArrayBuffer(file);
  }
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

大文件分片读取

在对于超大文件,一般采用分片上传的思路解决。文章开头有讲到,File 是 Blob 的一个特例。而 Blob 上有一个slice 方法,通过它,前端就可以实现分片读取大文件的操作。

为了方便说明,请先准备好一个 txt 文件,文件内容就是:hello world

示例代码如下,代码中只读取前 5 个字节,由于每个英文字母占 1 个字节,所以打印结果应该是“hello”。

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }
  // 为了方便说明,这里仅仅读取第一个文件
  const file = files[0];
  // 读取前5个字节的内容
  const blob = file.slice(0, 5);

  const reader = new FileReader();
  // 控制台输出结果应该是:hello
  reader.onload = ev => console.log(ev.target.result);
  reader.readAsText(blob);
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

拖拽上传

和前面所述的 File API 相关是完全一样的。唯一需要特殊处理的是文件对象的获取入口改变了。对于<input>标签,监听 onchange 事件,FileList 存放在 event.target.files 中;对于拖拽操作,FileList 存放在拖拽事件的回调函数参数里,通过 event.dataTransfer.files 访问即可。

需要修改一下 html 代码:

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <style>
    #container {
      width: 300px;
      height: 300px;
      border: 3px dotted red;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script src="./script.js"></script>
</body>
</html>

脚本文件的代码如下:

function handleDropover(event) {
  event.stopPropagation();
  event.preventDefault();
}

function handleDrop(event) {
  event.stopPropagation();
  event.preventDefault();
  /***** 访问拖拽文件 *****/
  const files = event.dataTransfer.files;
  console.log(files);
  /**********/
}

const target = document.querySelector("#container");
target.addEventListener("dragover", handleDropover);
target.addEventListener("drop", handleDrop);

后端相关

后端相关超出了本文的讨论范围,可以参考这篇文章

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP