说明

Web 应用程序的一个主要的痛点是无法操作用户计算机上的文件。

2000 年之前,处理文件的唯一方式是把放到一个表单里,仅此而已。

File API 与 Blob API 是为了让 Web 开发者能以安全的方式访问客户端机器上的文件,从而更好地与这些文件交互而设计的。

文件API关系图谱

JS - 文件API - 图1

==================

Blob 类型

Blob 继承自Object对象,表示二进制大对象(binary larget object),是 JavaScript 对不可修改二进制数据的封装类型。

包含字符串的数组、ArrayBuffers、ArrayBufferViews,甚至其他 Blob 都可以用来创建 blob。

创建

Blob构造函数可以接收一个 options 参数,并在其中指定的媒体类型 MIME

  1. console.log(new Blob(['foo']));
  2. // Blob {size: 3, type: ""}
  3. console.log(new Blob(['{"a": "b"}'], { type: 'application/json' }));
  4. // {size: 10, type: "application/json"}
  5. console.log(new Blob(['<p>Foo</p>', '<p>Bar</p>'], { type: 'text/html' }));
  6. // {size: 20, type: "text/html"}

Blob 对象有一个 size 属性和一个 type 属性

方法总览

image.png

选择部分数据(资源分段)

只读取部分文件可以节省时间,特别是在只需要数据特定部分比如文件头的时候

  1. let newblob = new Blob(['{"a": "b"}'],{ type: 'application/json' })
  2. // 格式为 slice(开始位置,结束位置,类型),都是可选的参数
  3. let sb = newblob.slice(0,4,{ type: 'application/json'}) // 只获取1-4字节的数据,返回的也是一个blob对象

File 类型

MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/File
每个 File 对象都有一些只读属性:
1、name:本地系统中的文件名,只读。
2、size:以字节计的文件大小,只读。
3、type:包含文件 MIME 类型的字符串。
4、lastModifiedDate:表示文件最后修改时间的字符串,不建议使用。
4、lastModified:返回当前 File 对象所引用文件最后修改时间,自 UNIX 时间起始值(1970年1月1日 00:00:00 UTC)以来的毫秒数。

FileReader 类型

FileReader类型表示一种异步文件读取机制。

可以把FileReader 想象成类似于XMLHttpRequest,只不过是用于从文件系统读取文件,而不是从服务器读取数据。

方法

FileReader 类型提供了几个读取文件数据的方法:
1、readAsText(file, encoding):从文件中读取纯文本内容并保存在 result 属性中。第二个参数表示编码,是可选的。
2、readAsDataURL(file):读取文件并将内容的数据 URI 保存在 result 属性中,Base64格式。(更适合展示图片)
3、readAsArrayBuffer(file):读取文件并将文件内容以 ArrayBuffer 形式保存在 result 属性。

钩子函数(过程方法)

因为这些读取方法是异步的,所以每个 FileReader 会发布几个事件,其中 3 个最有用的事件是onprogress、onerror 和 onload,分别表示还有更多数据、发生了错误和读取完成。

onprogress 事件每 50 毫秒就会触发一次,其与 XHR 的 progress 事件具有相同的信息:lengthComputable、loaded 和 total。
此外,在 progress 事件中可以读取 FileReader 的 result属性,即使其中尚未包含全部数据。

onerror 事件会在由于某种原因无法读取文件时触发。触发 error 事件时,FileReader 的 error属性会包含错误信息。这个属性是一个对象,只包含一个属性:code。这个错误码的值可能是 1(未找到文件)、2(安全错误)、3(读取被中断)、4(文件不可读)或 5(编码错误)。

onabort 如果想提前结束文件读取,则可以在过程中调用 abort()方法,从而触发 abort 事件。

onload 事件会在文件成功加载后触发。

onloadend load、error 和 abort 事件触发后,还会触发 loadend 事件,表示整个过程完全结束

例子

见下应用的展示

FileReaderSync 类型

FileReaderSync 类型就是 FileReader 的同步版本。

这个类型拥有与 FileReader相同的方法,只有在整个文件都加载到内存之后才会继续执行。

FileReaderSync 只在工作线程中可用,因为如果读取整个文件耗时太长则会影响全局。

假设通过 postMessage()向工作线程发送了一个 File 对象。以下代码会让工作线程同步将文件读取到内存中,然后将文件的数据 URL 发回来

  1. // worker.js
  2. self.omessage = (messageEvent) => {
  3. const syncReader = new FileReaderSync();
  4. console.log(syncReader); // FileReaderSync {}
  5. // 读取文件时阻塞工作线程
  6. const result = syncReader.readAsDataUrl(messageEvent.data);
  7. // PDF 文件的示例响应
  8. console.log(result); // data:application/pdf;base64,JVBERi0xLjQK...
  9. // 把 URL 发回去
  10. self.postMessage(result);
  11. };

URL 类型

对象 URL 有时候也称作 Blob URL,是指引用存储在 File 或 Blob 中数据的 URL。

对象 URL 的优点是不用把文件内容读取到 JavaScript 也可以使用文件。

只要在适当位置提供对象 URL 即可。
见下应用。

==============

应用

1、选择本地文件

选择文件

  1. // <input type="file" id="fileInput">
  2. // <input type="file" multiple id="fileInput"> 表示可以多选
  3. // <input type="file" multiple accept="image/*" id="fileInput"> 限定文件类型
  4. let fileInput = document.getElementById("fileInput");
  5. // 给input添加 change 改变事件,也就是用户选择的文件发生变化时触发
  6. fileInput.addEventListener("change", (event) => {
  7. console.log(event.target.files) // 获取选择后的fileList对象,里面就是file类型的对象,是个数组
  8. // 然后可以处理文件 event.target.files
  9. });

image.png

替换丑陋的原生

image.png(这是丑陋的原生input)

  1. // 1、通过style="display:none"隐藏input标签
  2. // <input type="file" id="fileInput" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
  3. // 2、设置你想要的按钮
  4. // <button id="myBtn">点我选择文件</button>
  5. // 3、获取input 和 按钮
  6. const myBtn = document.getElementById("myBtn");
  7. const fileInput = document.getElementById("fileInput");
  8. // 4、给按钮添加点击事件,然后触发input的click() 方法
  9. myBtn.addEventListener("click", function (e) {
  10. if (fileInput) {
  11. fileInput.click();
  12. }
  13. }, false);
  14. // 5、给input添加 change 改变事件,也就是用户选择的文件发生变化时触发
  15. fileInput.addEventListener("change", (event) => {
  16. console.log(event.target.files) // 获取选择后的fileList对象,里面就是file类型的对象,是个数组
  17. // 然后可以处理文件 event.target.files
  18. });

拖放选择文件

在页面上创建放置目标后,可以从桌面上把文件拖动并放到放置目标。

这样会像拖放图片或链接一样触发 drop 事件。

被放置的文件可以通过事件的 event.dataTransfer.files 属性读到,这个属性保存着一组 File 对象,就像文本输入字段一样。

  1. // 1、获取把文件拖放到的元素
  2. let droptarget = document.getElementById("droptarget");
  3. // 2、处理拖放事件的函数
  4. function handleEvent(event) {
  5. let info = "",
  6. output = document.getElementById("output"),
  7. files, i, len;
  8. // 阻止默认事件
  9. event.preventDefault();
  10. if (event.type == "drop") {
  11. // 可以通过 event.dataTransfer.files 读到文件,是个数组
  12. files = event.dataTransfer.files;
  13. // 然后使用文件
  14. i = 0;
  15. len = files.length;
  16. while (i < len) {
  17. info += `${files[i].name} (${files[i].type}, ${files[i].size} bytes)<br>`;
  18. i++;
  19. }
  20. output.innerHTML = info;
  21. }
  22. }
  23. // 必须取消 dragenter、dragover 和 drop 的默认行为
  24. droptarget.addEventListener("dragenter", handleEvent);
  25. droptarget.addEventListener("dragover", handleEvent);
  26. droptarget.addEventListener("drop", handleEvent);

2、展示

FileReader API 读取文件并展示

1、用户选择文件后,可以通过event.target.files 获取用户选择的文件。
2、使用FileReader 对象,可以读取File类型的对象和Blob类型的对象,
(1)调用readAsDataURL方法,可以获得”data : URL”格式的字符串(base64编码),这个直接放在JS - 文件API - 图5图片标签里的src上,就可以直接展示
image.png

(2)调用readAsText方法,可以获得文件内的文本,可以直接当做文本展示

  1. // HTML
  2. // <input type="file" name="fileList" id="files-list">
  3. // <div id="progress">读取进度</div>
  4. // <div id="output">展示</div>
  5. let filesList = document.getElementById("files-list"); // input 选择文件按钮
  6. let progress = document.getElementById("progress"); // 显示进度
  7. let output = document.getElementById("output"); // 显示输出信息
  8. // 1、添加改变事件,也就是用户选择了文件
  9. filesList.addEventListener("change", (event) => {
  10. let info = "", //输出信息
  11. // 2、获取用户选择的文件列表,类似数组
  12. let files = event.target.files;
  13. let type = "default"; // 读取的文件类型
  14. // 3、使用FileReader API,通过创建对象实例
  15. let reader = new FileReader();
  16. // 区分文件是图片还是文本
  17. if (/image/.test(files[0].type)) {
  18. reader.readAsDataURL(files[0]); // 调用readAsDataURL方法,更适合展示图片
  19. type = "image";
  20. } else {
  21. reader.readAsText(files[0]); // 调用readAsText方法,更适合文本
  22. type = "text";
  23. }
  24. // 4、设置读取错误的方法,用于显示读取错误信息
  25. reader.onerror = function() {
  26. output.innerHTML = "无法读取文件,错误码为 " + reader.error.code;
  27. };
  28. // 5、设置读取中方法,用于显示读取中的提示,每50毫秒触发一次
  29. reader.onprogress = function(event) {
  30. // event.lengthComputable:文件大小是否是可计算的(布尔值)
  31. // event.loaded:当前已加载的字节数
  32. // event.total:总共加载的字节数
  33. if (event.lengthComputable) {
  34. progress.innerHTML = `${event.loaded}/${event.total}`;
  35. }
  36. };
  37. // 6、设置读取完成方法,用于展示结果
  38. reader.onload = function() {
  39. let html = "";
  40. switch(type) {
  41. case "image":
  42. html = `<img src="${reader.result}">`;
  43. break;
  44. case "text":
  45. html = reader.result;
  46. break;
  47. }
  48. output.innerHTML = html;
  49. };
  50. });

URL 对象展示图片

好处就是不需要先读取再展示,而是直接展示

  1. // HTML
  2. // <input type="file" name="fileList" id="files-list">
  3. // <div id="progress">读取进度</div>
  4. // <div id="output">展示</div>
  5. let filesList = document.getElementById("filesList");
  6. let progress = document.getElementById("progress");
  7. let output = document.getElementById("output");
  8. // 1、添加改变事件,也就是用户选择了文件
  9. filesList.addEventListener("change", (event) => {
  10. let info = "";
  11. // 2、获取用户选择的文件列表,类似数组
  12. let files = event.target.files;
  13. // 3、通过URL对象展示
  14. let url = window.URL.createObjectURL(files[0]);
  15. //
  16. if (url) {
  17. if (/image/.test(files[0].type)) {
  18. output.innerHTML = `<img src="${url}">`;
  19. } else {
  20. output.innerHTML = `<a href="${url}" download="文件名.jpg">点击下载</a>`;
  21. }
  22. } else {
  23. output.innerHTML = "你的浏览器不支持 URL 对象";
  24. }
  25. });

image.png

提前释放展示的文件(可选)
页面卸载时,所有对象 URL 占用的内存都会被释放。不过,最好在不使用时就立即释放内存,以便尽可能保持页面占用最少资源。

  1. // window.URL.revokeObjectURL(files[0]);
  2. // 可以展示完图片后释放
  3. document.getElementById('f').addEventListener('change', function (e) {
  4. var file = this.files[0];
  5. const img = document.getElementById('img');
  6. const url = window.URL.createObjectURL(file);
  7. img.src = url;
  8. img.onload = function () {
  9. // 释放一个之前通过调用 URL.createObjectURL创建的 URL 对象
  10. window.URL.revokeObjectURL(url);
  11. }
  12. }, false);

3、上传

FormData 通过 Axios 上传

现代前端开发,一般都用前端框架,如Vue、React等,会用Axios这些发送请求的第三方库。
然后创建表单数据对象FormData,给表单对象添加文件数据,然后发送

  1. // HTML
  2. // <input type="file" name="fileList" id="files-list">
  3. import axiso from 'axios'
  4. let filesList = document.getElementById("filesList");
  5. function update(event){
  6. let file = event.target.files[0];
  7. let param = new FormData(); // 创建FormData对象
  8. param.append('file',file);// 通过append向form对象添加数据,第一个参数是key;第二个参数是value,可以传入一个Blob 或 File 类型对象
  9. // 添加请求头
  10. let config = {
  11. headers:{'Content-Type':'multipart/form-data'}
  12. };
  13. // 通过Axios模块发送请求给后端
  14. axiso.post('http://后端接口',param,config)
  15. .then(response=>{
  16. console.log(response.data);
  17. })
  18. }
  19. filesList.addEventListener("change", update)

4、下载

标签下载

原理
1、通过服务器发过来的文件、图片,可以是Blob或File类型的
2、创建一个a标签元素,然后把 a.href = URL.createObjectURL(blob),通过URL对象创建一个URL,赋值给a标签的地址href
3、创建一个模拟鼠标点击事件,模拟点击a标签,触发下载

  1. // 1、创建a标签
  2. let a = document.createElement('a')
  3. // 2、通过URL对象创建一个URL,赋值给a标签的地址href
  4. a.href = URL.createObjectURL(blob)
  5. // 模拟点击a标签弹出下载框
  6. a.dispatchEvent(new MouseEvent('click'))

第三方库 FileSaver.js

github说明:https://github.com/eligrey/FileSaver.js
原理:就是通过a标签触发点击事件下载,只是做了很多不同浏览器的边界判断

安装

npm install file-saver —save
或者cdn引入

  1. <script src="./public/cdn/file/FileSaver.js"></script>

使用

主要是通过函数saveAs(blob , name , [选项] , [弹出框]) 进行操作
选项只有:{ autoBom: true },转换成Unicode,只有blob type 为 charset=utf-8 有效。

通过二进制格式blob,保存成文件

  1. var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
  2. saveAs(blob, "hello world.txt");//其他格式也可以,比如表格xlsx

保存成图片

  1. var canvas = document.getElementById("my-canvas")
  2. canvas.toBlob(function(blob) {
  3. saveAs(blob, "pretty image.png");
  4. });