前言
在前端开发时,经常会遇到一些和文件相关的场景。比如:文件下载、文件上传、文件预览等。下面我们就列举一些实际场景,通过这些场景介绍一下文件相关的 API及文件各种形式间的转换。
实际场景
文件下载
下载场景一:后端提供文件路径 www. xx.com/download/file.xlsx,希望可以下载。( GET 请求方式,返回文件流)可以通过 window.open, location.href, a 标签下载
window.open
打开新页签下载文件
// 适用下载场景一
const url = 'www.xx.com/download/file.xlsx';
window.open(url);
window.location.href
当前页面下载文件
// 适用下载场景一
const url = 'www.xx.com/download/file.xlsx';
window.location.href = url;
a 标签
<!-- 适用下载场景一 -->
<!-- 当前页面下载文件 -->
<a href={url}>下载</a>
<!-- 打开新页面下载文件 -->
<a href={url} target="_blank">下载</a>
<!-- 下载文件并对文件重命名;对于浏览器支持预览类型的文件可以增加 download 属性强制下载 -->
<a href={url} download="file.txt">下载</a>
// 通过动态创建 a 标签下载文件
function downloadFile (fileUrl, filename) {
const aLink = document.createElement('a');
aLink.download = filename;
// 对于浏览器支持预览类型的文件可以增加 download 属性强制下载
aLink.href = fileUrl;
document.body.appendChild(aLink);
aLink.click();
document.body.removeChild(aLink);
}
- GET 方式下载优点:
- 使用简单
- GET 方式下载缺点:
- 直接下载,无法知道下载的进度和状态
- url 长度限制。如果下载文件需要附加额外参数,那么加在 url 上的参数可能存在超出 url 长度的问题。
- 参数信息展示在 url 上
- 不能设置 header
- 浏览器通过响应头中 content-type 区分文件类型,可浏览的文件不会下载而是在页面展示。比如 png、txt、js 等。可以使用 a 标签 download 属性解决预览文件的问题
- content-type 类型是浏览器支持预览的(如 image/jpeg 、text/plain 等),则自动预览。
- content-type 为 application/octet-stream 时,下载文件,但是不能识别文件类型,下载的文件名为接口名
下载文件场景二:随着页面使用人数变多,需求变更:不同用户下载不同文件。为了满足需求,后端提供了 post 接口 '/download' ,通过传入参数不同返回不同文件。(post 请求,返回文件流)返回文件流时,需要将文件流转换成 url
- 从响应头中 content-type 包含的 application/octet-stream 可以看出为二进制流类型
- 从 content-disposition 中包含的 filename 可以获取到文件名
// 适用下载场景二
function getFilename (ajaxRes) {
// content-disposition 还有一种形式 filename*=,下方链接可查看具体内容
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
return decodeURI(ajaxRes.response.headers['content-disposition'])
.split('filename=')[1];
}
function streamToUrl (stream) {
const blob = new Blob(
[stream],
{type: 'application/octet-stream'},
);
return URL.createObjectURL(blob);
}
// 获取 post 请求的响应数据
axios.post('/download',{data: {}).then(ajaxRes => {
const filename = getFilename(ajaxRes);
const fileUrl = streamToUrl(ajaxRes.response.data);
downloadFile(fileUrl, filename);
});
- POST 方式下载优点
- 可以获取下载的进度和状态
- 可以设置 header
- 浏览器可浏览文件也能下载
- POST 方式下载缺点
- 兼容性问题,ie10 以下不可用
文件上传
Form 上传
上传文件场景一:很久以前,上传文件通过 form 表单的方式,这种方式兼容性好,低版本浏览器也可以支持。
<form action="请求地址" method="post" enctype="multipart/form-data">
<input type="file" name="" />
<input type="text" name="" />
<input type="submit" value="提交" />
</form>
FormData 上传
上传文件场景二:现在的上传功能,普遍通过 FormData 这种方式。
<!-- 单文件上传 -->
<input type="file" id="uploadFile" />
<!-- 多文件上传 -->
<input type="file" id="uploadFile" multiple />
<!-- 限制上传文件类型 -->
<input type="file" id="uploadFile" accept=".jpg, .png" />
const uploadFile = document.getElementById("uploadFile");
const files = uploadFile.files;
if (!files.length) {
return;
}
const formData = new FormData();
formData.append('file', files[0]);
axios.post('/upload', formData).then(ajaxRes => {
console.log(ajaxRes);
});
文件预览
预览文件场景一:后端给了文件路径 希望前端预览文件。可以直接将文件路径赋值给 img,iframe,video 等标签。
<!-- 图片预览 -->
<img src="文件路径" />
<!-- pdf 预览 -->
<iframe src="文件路径" />
<!-- 视频预览 -->
<video controls src="文件路径" />
预览文件场景二:在上传文件时可以预览文件。可以通过 Blob 或者 File 实例预览,需要将实例转换成 url 展示。
关键词:通过 FileReader, URL.createObjectURL 转换成 url
// 生成 图片 DOM
const img = document.querySelector('#img');
// 方法一
function preview(blob, img) {
img.src = URL.createObjectURL(blob); // 生成 Object URL
img.onload = function (e) {
URL.revokeObjectURL(this.src);
}
}
// 方法二
function preview(blob, img) {
const reader = new FileReader(blob);
reader.readAsDataURL(blob); // 生成 base64
reader.onload = function () {
img.src = this.result;
}
}
const input = document.getElementById("uploadFile");
input.addEventListener('change',function(){
const files = this.files;
preview(files[0], img);
},false);
场景三:想要预览图片,并且还希望对图片进行一些操作。需要使用 canvas。比如:给我们一个图片地址 './imgs/img1.png'
Canvas
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.src = './imgs/img1.png';
img.setAttribute('crossOrigin', 'Anonymous');
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.drawImage(img, 0, 0, img.width, img.height);
}
上边代码通过 Image 对象和 canvas API 将图片渲染在画布上。
后面就可以使用 canvas API 在画布上进行各种想要的操作,比如画各种图形,裁剪图片等等。
还可以通过 canvas API 将 canvas 转换成 Blob 对象,传递给后台进行保存。
上面我们简单列举了一下常见的处理文件的场景。可以看到一些反复出现的 API。比如 Blob,File,FileReader, URL.createObjectURL, base64, Canvas,FormData。下面我们就介绍一下这些 API。
API 介绍
Blob
定义: Blob
对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
构造函数(Blob)
// new Blob(array, options);
const blob = new Blob(['blob instance'], {type: 'text/plain'});
参数:
-
array: 由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成
-
options(可选):
- type: 默认值为
""
,它代表了将会被放入到 blob 中的数组内容的 MIME 类型。 - endings: 默认值为
"transparent"
,用于指定包含行结束符\n
的字符串如何被写入。它是以下两个值中的一个:"native"
,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者"transparent"
,代表会保持 blob 中保存的结束符不变
- type: 默认值为
实例(blob)
-
属性:
- size: 所包含数据的字节数。
- type: 属性给出文件的 MIME 类型。如果类型无法确定,则返回空字符串。
-
方法:
- blob.arrayBuffer(): 返回 Promise 对象,以二进制数据的形式呈现。
- blob.stream():返回一个 ReadableStream 对象。
- blob.text(): 返回 Promise 对象,使用 UTF-8 格式编码。
- blob.slice([start [, end [, contentType]]]}: 创建一个包含源 Blob 指定字节范围内的数据的新 Blob 对象。
File
构造函数
// new File(bits, name[, options]);
const file = new File(['file instance'], 'file', {type: 'text/plain'});
参数:
-
bits: 同 Blob 构造函数的第一个参数
-
name: 表示文件名称,或者文件路径
-
options(可选):
- type: 表示将要放到文件中的内容的 MIME 类型。默认值为
""
- lastModified: 数值,表示文件最后修改时间的 Unix 时间戳(毫秒)。默认值为 Date.now()。
- type: 表示将要放到文件中的内容的 MIME 类型。默认值为
除了 File 构造函数可以得到 File 对象外,还可以通过 input[type='file'] 标签得到 File 对象。
input[type='file']
<input type="file" id='uploadInput' />
标签属性
- multiple 允许用户选择多个文件
- accept 指定可接受的文件类型,它是一个以逗号间隔的文件扩展名和 MIME 类型列表。
监听事件
- onchange 通过时间对象中 event.target.files 获取到 FileList 数组(及多个 File 对象)。
MIME 类型
Blob 和 File 的构造函数参数和 input 的 accept 属性中都提到了 MIME 类型。这里就简单介绍一下 MIME 类型。
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式。它在IETF RFC 6838中进行了定义和标准化。
类型 | 描述 | 典型示例 |
---|---|---|
text | 表明文件是普通文本,理论上是人类可读 | text/plain , text/html , text/css, text/javascript |
image | 表明是某种图像。不包括视频,但是动态图(比如动态 gif)也使用 image 类型 | image/gif , image/png , image/jpeg , image/bmp , image/webp , image/x-icon , image/vnd.microsoft.icon |
audio | 表明是某种音频文件 | audio/midi , audio/mpeg, audio/webm, audio/ogg, audio/wav |
video | 表明是某种视频文件 | video/webm , video/ogg |
application | 表明是某种二进制数据 | application/octet-stream , application/pkcs12 ,application/vnd.mspowerpoint , application/xhtml+xml ,application/xml , application/pdf |
FileReader
FileReader 异步读取文件内容,并转换成不同的文件形式。
构造函数(FileReader)
const reader = new FileReader();
实例(reader)
-
属性
- reader.error 一个DOMException,表示在读取文件时发生的错误。
- reader.result 文件的内容。
- reader.readyState 表示
FileReader
状态的数字
常量名 | 值 | 描述 |
---|---|---|
EMPTY | 0 | 还没有加载任何数据。 |
LOADING | 1 | 数据正在被加载。 |
DONE | 2 | 已完成全部的读取请求。 |
-
方法
- reader.abort 中止读取操作。在返回时,
readyState
属性为DONE
。 - reader.readAsArrayBuffer 转成 ArrayBuffer 数据对象。
- reader.readAsBinaryString 转成二进制数据
- reader.readAsDataURL 转成 Base64 url
- reader.readAsText 转成给定编码的文本字符串
- reader.abort 中止读取操作。在返回时,
-
事件
- abort 该事件在读取操作被中断时触发。
- error 该事件在读取操作发生错误时触发。
- load 该事件在读取操作完成时触发。
- loadend 该事件在读取操作开始时触发。
- loadstart 该事件在读取操作结束时(要么成功,要么失败)触发。
- progress 该事件在读取Blob时触发。
URL.createObjectURL
创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。
// URL.createObjectURL(blob/file/mediaSource);
const file = new File(['this is a file'], 'file', {type: 'text/plain'});
const objURL = URL.createObjectURL(file);
Base64
编码格式,在前端经常会碰到,格式是 data:[<mediatype>][;base64],<data>
window.atob(str)
函数能够解码通过 base-64 编码的字符串数据
window.btoa(base64)
函数能够从二进制数据“字符串”创建一个 base-64 编码的 ASCII 字符串
Canvas
构造函数
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
实例
-
方法
- 绘制线段
ctx.moveTo
、ctx.lineTo
- 绘制矩形
ctx.fillRect
、ctx.strokeRect
- 清除矩形区域
ctx.clearRect
- 绘制弧形
ctx.arc
- 绘制文本
ctx.strokeText
、ctx.fillText
- 保存和恢复状态
ctx.save
、ctx.restore
- 绘制线段
FormData
FormData
接口提供了一种表示表单数据的键值对 key/value
的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data"
,它会使用和表单一样的格式。
构造函数
const formData = new FormData(form);
实例
-
方法
- formData.append 会添加一个新值到
FormData
对象内的一个已存在的键中,如果键不存在则会添加该键。 - formData.delete 会从 FormData 对象中删除指定键和它对应的值。
- formData.entries 返回一个 iterator 对象,此对象可以遍历访问 FormData 中的键值对
- formData.get 用于返回 FormData 对象中和指定的键关联的第一个值
- formData.has 返回一个布尔值,表示该 FormData 对象是否含有某个 key。
- formData.set 对 FormData 对象里的某个 key 设置一个新的值,如果该 key 不存在,则添加
- formData.append 会添加一个新值到
不同数据类型之间互相转换
blob 转成 file(通过 File)
function blobToFile(blob) {
return new File([blob], "file", { type: blob.type });
}
file 转成 blob(通过 Blob)
function fileToBlob(file) {
return new Blob([file], { type: file.type });
}
blob/file 转成 Base64(通过 FileReader)
function blobToBase64(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
blob/file 转成 Object URL (通过 URL.createObjectURL)
const url = URL.createObjectURL(blob);
canvas 转成 Base64
let url = canvas.toDataURL("image/png");
Base64 转成 blob
function base64ToBlob(base64) {
const arr = base64.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
const len = bstr.length;
const u8arr = new Uint8Array(len);
while (len--) {
u8arr[len] = bstr.charCodeAt(len);
}
return new Blob([u8arr], { type: mime });
}
Base64 转成 file
function base64ToFile(base64, filename) {
const arr = base64.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const suffix = mime.split('/')[1] ;
const bstr = atob(arr[1]);
const len = bstr.length;
const u8arr = new Uint8Array(len);
while (len--) {
u8arr[len] = bstr.charCodeAt(len);
}
return new File([u8arr], `${filename}.${suffix}`, { type: mime });
}
总结
- Blob, File 用来存储二进制数据
- FileReader 将数据转换成各种形式
- URL.createObjectURL 将 blob/file 实例转换成 URL
- 上传文件时,用 FormData 将数据传递给后端
- Canvas 用来修改图片
- Blob、File、FileReader、FormData 、URL.createObjectURL 在 IE10 以下都不兼容
- Canvas 在 IE9 以下不兼容