前端怎么处理文件

lxf2023-03-10 07:57:01

前言

前端开发时,经常会遇到一些和文件相关的场景。比如:文件下载、文件上传、文件预览等。下面我们就列举一些实际场景,通过这些场景介绍一下文件相关的 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 中保存的结束符不变

实例(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()。

前端怎么处理文件

除了 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状态的数字
常量名描述
EMPTY0还没有加载任何数据。
LOADING1数据正在被加载。
DONE2已完成全部的读取请求。
  • 方法

    • reader.abort 中止读取操作。在返回时,readyState属性为DONE
    • reader.readAsArrayBuffer 转成 ArrayBuffer 数据对象。
    • reader.readAsBinaryString 转成二进制数据
    • reader.readAsDataURL 转成 Base64 url
    • reader.readAsText 转成给定编码的文本字符串
  • 事件

    • 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.moveToctx.lineTo
    • 绘制矩形 ctx.fillRectctx.strokeRect
    • 清除矩形区域 ctx.clearRect
    • 绘制弧形 ctx.arc
    • 绘制文本 ctx.strokeTextctx.fillText
    • 保存和恢复状态 ctx.savectx.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 不存在,则添加

不同数据类型之间互相转换

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 以下不兼容