文件上传
大约 6 分钟
文件上传
文件上传实质上就是将文件内容通过http
请求传递到服务器,至于上传类型多种多样,比如:
FormData
:这种方式要设置请求头为multipart/form-data
Base64
:这种方式要设置请求头为application/json
二进制数据
:这种方式要设置请求头为application/octet-stream
至于其他类型,可以根据接口的具体要求来。
于前端而言,文件上传这个功能分为两部分,第一是文件上传这个组件的样式和交互,第二就是实现上传文件到服务器。我们这里不纠结样式问题,只关注上传文件这一本质的内容。
注
下面实现功能是,都采用原生的Ajax
,且都是基本的点击input
标签实现,至于其他的接口请求方式如axios | fetch
都大差不差。
FormData
/**
* @description: 单个文件上传函数
* @param {File} file 文件数据
* @param {Function} onProgress 上传进度处理函数
* @param {Function} onFinished 上传完成处理函数
* @return {Function} 取消上传的方法
*/
function upload(file, onProgress, onFinish) {
// 使用 FormData 构造函数,它就会自动设置请求头的一些配置
const form = new FormData()
// 往 form 中添加字段,可以添加多个。这里是单文件,所以就一个
// 这里的 "file" 是上传文件的字段名,根据接口要求来
form.append("file", file)
const xhr = new XMLHttpRequest()
// 监听请求完成
xhr.onload = () => {
// 这里获取服务器返回结果
const res = JSON.parse(xhr.responseText)
console.log(res)
// 返回服务器发送过来的数据
onFinish(res)
}
// 监听请求进度
xhr.upload.onprogress = (e) => {
// 通过这个参数 e,就能拿到目前上传的一些信息,
// 如文件总大小(total)、目前已上传的大小(loaded)
console.log(e)
// 调用进度处理函数
onProgress(e)
}
// 请求配置:类型,路径
xhr.open("post", "http://test.com:8888/upload")
xhr.send(form)
// 返回取消函数
return function () {
// 取消请求
xhr.abort()
}
}
Base64
这种方式上传一般是一个json
类型的数据,需要告诉服务器文件的后缀名、文件的内容,这里我们规定数据格式如下:
{
"ext": "", // 如 .png .jpg等
"file": "" // 文件内容,base64 格式字符串
}
这个方式与FormData
的区别就在于处理文件格式、请求头,其他的请求流程等都一致,如下:
/**
* @description: 单个文件上传函数
* @param {File} file 文件数据
* @param {Function} onProgress 上传进度处理函数
* @param {Function} onFinished 上传完成处理函数
* @return {Function} 取消上传的方法
*/
function upload(file, onProgress, onFinish) {
// 全局提供 xhr,以便返回取消函数
let xhr = null
// 获取文件内容的 base64 格式
// FileReader 用来读取文件
const reader = new FileReader()
reader.onload = (e) => {
// 获取文件后缀名
const ext = "." + file.name.split(".").pop()
// e.target.result 就是文件的 base64 格式的本地路径
// 我们需要去除这个路径前面的部分,只拿 base64 格式的内容部分
const base64 = e.target.result.split(",").pop()
const fileData = JSON.stringify({
ext: ext,
file: base64
})
xhr = new XMLHttpRequest()
// 监听请求完成
xhr.onload = () => {
// 这里获取服务器返回结果
const res = JSON.parse(xhr.responseText)
console.log(res)
// 返回服务器发送过来的数据
onFinish(res)
}
// 监听请求进度
xhr.upload.onprogress = (e) => {
// 通过这个参数 e,就能拿到目前上传的一些信息,
// 如文件总大小(total)、目前已上传的大小(loaded)
console.log(e)
// 调用进度处理函数
onProgress(e)
}
// 设置请求头
xhr.setRequestHeader("content-type", "application/json")
// 请求配置:类型,路径
xhr.open("post", "http://test.com:8888/upload")
xhr.send(fileData)
}
// 它是异步的方法,因此在上面使用 onload 获取完成后的文件数据并发送请求
reader.readAsDataURL(file)
// 返回取消函数
return function () {
// 取消请求
xhr.abort()
}
}
注
这里使用了FileReader
,关于它可以查看:FileReader - Web API 接口参考 | MDN (mozilla.org)。
直接发送二进制内容
这种方式与上面的方式的区别也就是请求头和文件处理格式不一致,如下
/**
* @description: 单个文件上传函数
* @param {File} file 文件数据
* @param {Function} onProgress 上传进度处理函数
* @param {Function} onFinished 上传完成处理函数
* @return {Function} 取消上传的方法
*/
function upload(file, onProgress, onFinish) {
// 获取文件后缀名
const ext = "." + file.name.split(".").pop()
const xhr = new XMLHttpRequest()
// 监听请求完成
xhr.onload = () => {
// 这里获取服务器返回结果
const res = JSON.parse(xhr.responseText)
console.log(res)
// 返回服务器发送过来的数据
onFinish(res)
}
// 监听请求进度
xhr.upload.onprogress = (e) => {
// 通过这个参数 e,就能拿到目前上传的一些信息,
// 如文件总大小(total)、目前已上传的大小(loaded)
console.log(e)
// 调用进度处理函数
onProgress(e)
}
// 设置请求头
xhr.setRequestHeader("content-type", "application/octet-stream")
// 设置文件后缀名
xhr.setRequestHeader("x-ext", ext)
// 请求配置:类型,路径
xhr.open("post", "http://test.com:8888/upload")
xhr.send(file)
// 返回取消函数
return function () {
// 取消请求
xhr.abort()
}
}
拓展
推拽上传
拖拽上传这种方式,我们觉得没思路的地方可能就是拖拽这个方式,但其实<input />
原生就支持拖拽选择文件。因此要实现这种方式其实我们只需要将input
的宽高设置为100%
即可。
但是,这种方式其实也是有一点小问题的,因为可能有的浏览器不支持input
拖拽选择文件,那么我们就可以有另一种方式:
假设我们的input
的父元素为一个div
,那么我们就要将div
设置为一个可拖放的元素,这就需要用到HTML 拖放 API:
const div = document.querySelector("div")
// 拖动进入这个元素时触发一次
div.addEventListener("dragenter", function (e) {
// 阻止事件默认行为,因为默认情况下 div 不是一个可拖放目标
e.preventDefault()
})
// 拖动在这个元素上悬停时持续触发
div.addEventListener("dragover", function (e) {
// 阻止事件默认行为,因为默认情况下 div 不是一个可拖放目标
e.preventDefault()
})
// 拖动在这个元素上时松开鼠标,即将内容放置在这个元素上时触发
div.addEventListener("drop", function (e) {
// 阻止事件默认行为,因为默认情况下 div 不是一个可拖放目标
e.preventDefault()
// 获取拖放到这个元素上的内容,这里我们获取文件
const files = e.dataTransfer.files
console.log(files)
// 限制只能放置一个文件
if (files.length !== 1) {
alert("只能放置一个文件")
return false
}
// 限制只能放文件,因为页面上有别的元素默认是可以拖拽的,如图片
const fileTypes = e.dataTransfer.types
if (!fileTypes.includes("files")) {
alert("只能放置文件")
return false
}
// ... 其他的根据具体要求写,这里也要调用其他方法来触发上传文件这个操作
})