前言 🍊🍊
在上一篇文章中我们讲解了如何进行图片的一个压缩处理,里面涉及到很多与文件相关的知识,我们在实际项目开发中可能会用到文件上传操作,今天就来学习一下如何实现文件的切片上传。
文件是如何上传的?
首先我们要知道一般上传文件有两种方式:
- 基于
FormData
的格式来进行文件的上传 - 基于
Base64
的格式来进行上传
这两种方式上传各有优缺点:
Base64上传
:
- 优点:
- 简单易用:将文件转换为Base64字符串,然后将其作为文本数据发送,不需要额外的表单数据。 * 跨平台兼容:Base64是一种文本格式,可以在任何支持文本传输的平台上使用。 * 不需要额外的文件流处理:Base64数据可以直接包含在请求的主体中,不需要使用文件流进行处理。
- 缺点:
- base64上传的最大缺点就是他会是原本的文件的体积增大约1/3,这对于大文件上传尤其不利
- 由于文件的体积增大,上传的效率自然也就变低
FormData上传
:
- 优点:
- 支持多文件上传:通过FormData可以同时上传多个文件,方便批量上传。
- 文件流处理:FormData可以使用文件流的方式上传文件,适用于大型文件或流式传输的场景。
- 可以携带其他表单数据:FormData可以同时携带其他表单数据,方便与文件相关的其他数据一起传输。
- 缺点:
- 使用稍复杂:相比Base64上传,使用FormData上传需要创建一个FormData对象,然后将文件和其他表单数据附加到该对象上。
- 需要服务器端处理:使用FormData上传需要服务器端进行相应的解析和处理,提取上传的文件和表单数据。
了解一些文件相关的知识:🎮🎮
我们经常能够看到一些文件相关的知识例如 FlieReader,File,Blob等等相关的知识,其实可以用一张图来说明:
Blob
Blob 全称为 binary large object ,即二进制大对象。blob对象本质上是js中的一个对象,里面可以储存大量的二进制编码格式的数据。
blob对象上面一般会有两个属性:
size:Blob对象中所包含数据的大小(字节);
type:字符串,认为该Blob对象所包含的 MIME 类型。如果类型未知,则为空字符串。
同时我们应该要注意到Blob 对象内置了 slice() 方法用来将 blob 对象分片
start:设置切片的起点,即切片开始位置。默认值为 0,这意味着切片应该从第一个字节开始;
end:设置切片的结束点,会对该位置之前的数据进行切片。默认值为blob.size; 这样我们就可以通过slice方法来完成对blob数据的切片操作,切片之后的数据还会返回一个blob类型的数据。
File类型
Flie类型其实是特殊类型的Blob类型
当我们点击input中选择文件之后会返回一个FlieList对象
也可以通过拖拽操作生成的 DataTransfer 对象;
FileReader对象
使用FileReader对象的目的就是因为Blob中的数据是无法读取的,因此我们要通过FileReader中的方法来转换为我们想要读取的数据类型
readAsArrayBuffer(file)
:按字节读取文件内容,结果用ArrayBuffer对象表示readAsBinaryString(file)
:按字节读取文件内容,结果为文件的二进制串readAsDataURL(file)
:读取文件内容,结果用data:url的字符串形式表示readAsText(file,encoding)
:按字符读取文件内容,结果用字符串形式表示
在知道这些基础的知识之后我们就可以尽心大文件的切片上传操作
大文件的切片上传 🍯🍯
首先大家肯定会有疑问:什么是切片上传?
故名思意就是将一些大的文件切成一份一份的,来进行分开的上传操作,我们可以自定义切片的大小,然后后端在拿到数据之后在拼接起来。将其组装成为一个数据。
由于代码稍微有一点多,因此我们将其拆分成及部分来看
((doc) => { const selectFile = doc.querySelector("#selectFile"); const progress = doc.querySelector("#progress"); const uploadFile = doc.querySelector("#uploadFile"); // 绑定点击事件 function BindEvent() { selectFile.addEventListener("change", selectFuc); uploadFile.addEventListener("click", uploadSlice); } .... BindEvent(); })(document)
- 首先我们写一个立即执行函数,将一些初始化操作先完成,我们首先获取上传文件的input框,以及进度条和上传按钮的DOM元素。
- 然后我们给元素绑定点击事件,单独将这部分抽离出来,是整体看起来更加的规范。
const UPLOAD_TYPE = { "image/png": "png", "image/jpg": "jpg", "image/jpeg": "jpeg", "video/mp4": "mp4", }; function selectFuc(e) { const File = e.target.files[0]; console.log(File); if (!UPLOAD_TYPE[File.type]) { alert("格式不合要求"); this.value = ""; return; }
- 然后我们完成校验函数的封装,当然这里我们只是进行了一个简单的封装,对文件的类型进行一个简单的校验。如果想要上传的文件按不符合要求,那么就弹出弹框提示,并且将input中的value赋值为空。
- 这里我们可以通过
e.target.file[0]
来获取上传的元素 - 我们这里将一些我们允许上传的文件单独写成了一个对象,这也是模块化的体现。
// 上传函数 async function uploadSlice() { //获取input选择上传的文件 const file = selectFile.files[0]; if (!file) { alert("上传内容步能为空"); return; } const { name, type, size } = file; // 定义切片的大小 const chunckSize = 64 * 1024; // 当前文件上传的大小 let currentSize = 0; progress.max = size; while (currentSize < size) { // 切片切出来的元素 const FileChunk = file.slice(currentSize, currentSize + chunckSize); console.log(FileChunk); const Form = createFormData({ name, size, type, FileChunk }); await axios.get("http://localhost:3000/upload", Form); currentSize += FileChunk.size; progress.value = currentSize; } }
然后我们就要封装最重要的函数:切片上传函数
- 首先我们首先判断是否选择了要上传的元素,如果没有选择要上传的元素,则弹出弹框并且return。
- 然后我们通过解构将file中的常用属性解构出来
- 紧接着我们设置切片的大小,这个大小可以自己选择,然后还要定义一个变量,保存的是当前的已经上传的元素的大小,初始值为0
- 我们这里给进度条的DOM元素设置max为元素的大小,这与之后的progress的value值要进行配合,这样我们可以看清楚上传的进度
- 然后创建Form元素,等会同样这里我们也封装了一个函数,等一会我们在展开说
- 然后我们就要使用silce来进行切片,返回的是被切成小段的元素,有趣的是,如果将这一段通过
FileReader
中的方法转换为base64的格式,同样可以显示出来图片,只不过是被裁掉了一截。思考一下,根据这个我们可以实现什么? - 然后我们通过axios给后端发送请求,注意这里我的后端就是通过express简单的搭了一个,感兴趣的同学也可以搞一个,但是这里我们只是学习一下思路,这个不是重点,我就不展开来讲。
- 然后每次发送完请求之后给
currentSize
加上切片上传的尺寸,表明当前已经完成的部分,然后给进度条的vaue赋值为currentSize。
封装创建FormData函数:
// 限制文件上传的类型 // 创建FormData function createFormData({ name, type, size, currentSize }) { const Form = new FormData(); Form.append("name", name); Form.append("type", type); Form.append("size", size); Form.append("currentSize", currentSize); return Form; }
这部分其实比较好理解,就是单独封装了一个函数,用来创建FormData,然后通过append讲这些属性追加进去,最后返回通过axios发送给后端。
总结:🤙🤙
通过这次学习,我们掌握了文件相关的知识,了解了文件的传输形式(FormData,base64),并且我们学习了如何进行大文件的切片上传,使自己有很多收获。