说明
Web 应用程序的一个主要的痛点是无法操作用户计算机上的文件。
2000 年之前,处理文件的唯一方式是把放到一个表单里,仅此而已。
File API 与 Blob API 是为了让 Web 开发者能以安全的方式访问客户端机器上的文件,从而更好地与这些文件交互而设计的。
文件API关系图谱
==================
Blob 类型
Blob 继承自Object对象,表示二进制大对象(binary larget object),是 JavaScript 对不可修改二进制数据的封装类型。
包含字符串的数组、ArrayBuffers、ArrayBufferViews,甚至其他 Blob 都可以用来创建 blob。
创建
Blob构造函数可以接收一个 options 参数,并在其中指定的媒体类型 MIME:
console.log(new Blob(['foo']));
// Blob {size: 3, type: ""}
console.log(new Blob(['{"a": "b"}'], { type: 'application/json' }));
// {size: 10, type: "application/json"}
console.log(new Blob(['<p>Foo</p>', '<p>Bar</p>'], { type: 'text/html' }));
// {size: 20, type: "text/html"}
Blob 对象有一个 size 属性和一个 type 属性
方法总览
选择部分数据(资源分段)
只读取部分文件可以节省时间,特别是在只需要数据特定部分比如文件头的时候
let newblob = new Blob(['{"a": "b"}'],{ type: 'application/json' })
// 格式为 slice(开始位置,结束位置,类型),都是可选的参数
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 发回来
// worker.js
self.omessage = (messageEvent) => {
const syncReader = new FileReaderSync();
console.log(syncReader); // FileReaderSync {}
// 读取文件时阻塞工作线程
const result = syncReader.readAsDataUrl(messageEvent.data);
// PDF 文件的示例响应
console.log(result); // data:application/pdf;base64,JVBERi0xLjQK...
// 把 URL 发回去
self.postMessage(result);
};
URL 类型
对象 URL 有时候也称作 Blob URL,是指引用存储在 File 或 Blob 中数据的 URL。
对象 URL 的优点是不用把文件内容读取到 JavaScript 也可以使用文件。
==============
应用
1、选择本地文件
选择文件
// <input type="file" id="fileInput">
// <input type="file" multiple id="fileInput"> 表示可以多选
// <input type="file" multiple accept="image/*" id="fileInput"> 限定文件类型
let fileInput = document.getElementById("fileInput");
// 给input添加 change 改变事件,也就是用户选择的文件发生变化时触发
fileInput.addEventListener("change", (event) => {
console.log(event.target.files) // 获取选择后的fileList对象,里面就是file类型的对象,是个数组
// 然后可以处理文件 event.target.files
});
替换丑陋的原生
(这是丑陋的原生input)
// 1、通过style="display:none"隐藏input标签
// <input type="file" id="fileInput" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
// 2、设置你想要的按钮
// <button id="myBtn">点我选择文件</button>
// 3、获取input 和 按钮
const myBtn = document.getElementById("myBtn");
const fileInput = document.getElementById("fileInput");
// 4、给按钮添加点击事件,然后触发input的click() 方法
myBtn.addEventListener("click", function (e) {
if (fileInput) {
fileInput.click();
}
}, false);
// 5、给input添加 change 改变事件,也就是用户选择的文件发生变化时触发
fileInput.addEventListener("change", (event) => {
console.log(event.target.files) // 获取选择后的fileList对象,里面就是file类型的对象,是个数组
// 然后可以处理文件 event.target.files
});
拖放选择文件
在页面上创建放置目标后,可以从桌面上把文件拖动并放到放置目标。
这样会像拖放图片或链接一样触发 drop 事件。
被放置的文件可以通过事件的 event.dataTransfer.files 属性读到,这个属性保存着一组 File 对象,就像文本输入字段一样。
// 1、获取把文件拖放到的元素
let droptarget = document.getElementById("droptarget");
// 2、处理拖放事件的函数
function handleEvent(event) {
let info = "",
output = document.getElementById("output"),
files, i, len;
// 阻止默认事件
event.preventDefault();
if (event.type == "drop") {
// 可以通过 event.dataTransfer.files 读到文件,是个数组
files = event.dataTransfer.files;
// 然后使用文件
i = 0;
len = files.length;
while (i < len) {
info += `${files[i].name} (${files[i].type}, ${files[i].size} bytes)<br>`;
i++;
}
output.innerHTML = info;
}
}
// 必须取消 dragenter、dragover 和 drop 的默认行为
droptarget.addEventListener("dragenter", handleEvent);
droptarget.addEventListener("dragover", handleEvent);
droptarget.addEventListener("drop", handleEvent);
2、展示
FileReader API 读取文件并展示
1、用户选择文件后,可以通过event.target.files 获取用户选择的文件。
2、使用FileReader 对象,可以读取File类型的对象和Blob类型的对象,
(1)调用readAsDataURL方法,可以获得”data : URL”格式的字符串(base64编码),这个直接放在图片标签里的src上,就可以直接展示
(2)调用readAsText方法,可以获得文件内的文本,可以直接当做文本展示
// HTML
// <input type="file" name="fileList" id="files-list">
// <div id="progress">读取进度</div>
// <div id="output">展示</div>
let filesList = document.getElementById("files-list"); // input 选择文件按钮
let progress = document.getElementById("progress"); // 显示进度
let output = document.getElementById("output"); // 显示输出信息
// 1、添加改变事件,也就是用户选择了文件
filesList.addEventListener("change", (event) => {
let info = "", //输出信息
// 2、获取用户选择的文件列表,类似数组
let files = event.target.files;
let type = "default"; // 读取的文件类型
// 3、使用FileReader API,通过创建对象实例
let reader = new FileReader();
// 区分文件是图片还是文本
if (/image/.test(files[0].type)) {
reader.readAsDataURL(files[0]); // 调用readAsDataURL方法,更适合展示图片
type = "image";
} else {
reader.readAsText(files[0]); // 调用readAsText方法,更适合文本
type = "text";
}
// 4、设置读取错误的方法,用于显示读取错误信息
reader.onerror = function() {
output.innerHTML = "无法读取文件,错误码为 " + reader.error.code;
};
// 5、设置读取中方法,用于显示读取中的提示,每50毫秒触发一次
reader.onprogress = function(event) {
// event.lengthComputable:文件大小是否是可计算的(布尔值)
// event.loaded:当前已加载的字节数
// event.total:总共加载的字节数
if (event.lengthComputable) {
progress.innerHTML = `${event.loaded}/${event.total}`;
}
};
// 6、设置读取完成方法,用于展示结果
reader.onload = function() {
let html = "";
switch(type) {
case "image":
html = `<img src="${reader.result}">`;
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});
URL 对象展示图片
好处就是不需要先读取再展示,而是直接展示
// HTML
// <input type="file" name="fileList" id="files-list">
// <div id="progress">读取进度</div>
// <div id="output">展示</div>
let filesList = document.getElementById("filesList");
let progress = document.getElementById("progress");
let output = document.getElementById("output");
// 1、添加改变事件,也就是用户选择了文件
filesList.addEventListener("change", (event) => {
let info = "";
// 2、获取用户选择的文件列表,类似数组
let files = event.target.files;
// 3、通过URL对象展示
let url = window.URL.createObjectURL(files[0]);
//
if (url) {
if (/image/.test(files[0].type)) {
output.innerHTML = `<img src="${url}">`;
} else {
output.innerHTML = `<a href="${url}" download="文件名.jpg">点击下载</a>`;
}
} else {
output.innerHTML = "你的浏览器不支持 URL 对象";
}
});
提前释放展示的文件(可选)
页面卸载时,所有对象 URL 占用的内存都会被释放。不过,最好在不使用时就立即释放内存,以便尽可能保持页面占用最少资源。
// window.URL.revokeObjectURL(files[0]);
// 可以展示完图片后释放
document.getElementById('f').addEventListener('change', function (e) {
var file = this.files[0];
const img = document.getElementById('img');
const url = window.URL.createObjectURL(file);
img.src = url;
img.onload = function () {
// 释放一个之前通过调用 URL.createObjectURL创建的 URL 对象
window.URL.revokeObjectURL(url);
}
}, false);
3、上传
FormData 通过 Axios 上传
现代前端开发,一般都用前端框架,如Vue、React等,会用Axios这些发送请求的第三方库。
然后创建表单数据对象FormData,给表单对象添加文件数据,然后发送
// HTML
// <input type="file" name="fileList" id="files-list">
import axiso from 'axios'
let filesList = document.getElementById("filesList");
function update(event){
let file = event.target.files[0];
let param = new FormData(); // 创建FormData对象
param.append('file',file);// 通过append向form对象添加数据,第一个参数是key;第二个参数是value,可以传入一个Blob 或 File 类型对象
// 添加请求头
let config = {
headers:{'Content-Type':'multipart/form-data'}
};
// 通过Axios模块发送请求给后端
axiso.post('http://后端接口',param,config)
.then(response=>{
console.log(response.data);
})
}
filesList.addEventListener("change", update)
4、下载
标签下载
// 1、创建a标签
let a = document.createElement('a')
// 2、通过URL对象创建一个URL,赋值给a标签的地址href
a.href = URL.createObjectURL(blob)
// 模拟点击a标签弹出下载框
a.dispatchEvent(new MouseEvent('click'))
第三方库 FileSaver.js
github说明:https://github.com/eligrey/FileSaver.js
原理:就是通过a标签触发点击事件下载,只是做了很多不同浏览器的边界判断
安装
npm install file-saver —save
或者cdn引入
<script src="./public/cdn/file/FileSaver.js"></script>
使用
主要是通过函数saveAs(blob , name , [选项] , [弹出框]) 进行操作
选项只有:{ autoBom: true },转换成Unicode,只有blob type 为 charset=utf-8 有效。
通过二进制格式blob,保存成文件
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
saveAs(blob, "hello world.txt");//其他格式也可以,比如表格xlsx
保存成图片
var canvas = document.getElementById("my-canvas")
canvas.toBlob(function(blob) {
saveAs(blob, "pretty image.png");
});