使用ajax提交图片,在这里做一下记录,方便以后查阅,如有不对,欢迎指正。包含在项目中使用的图片压缩。
使用到的技术点:
- 选择文件上传,使用
input标签的file类型 - 拖拽文件上传,
input标签也可以通过拖拽上传,这里用div。通过ondrop事件的e.dataTransfer.files获取文件 - 图片限制(大小、类型、像素大小)
FileReader对象读取文件内容- 图片压缩(
image-conversion)npm包 - 图片预览
FormData对象,构造表单数据axios上传
input标签上传图片
<input type="file" @change="inputChange">
设置input的type类型为file。file类型的input有如下属性:
accept,用于规定文件上传控件中期望的文件类型,accept="image/*"capture,文件上传控件中媒体拍摄的方式。当文件类型为图片或视频且在移动端时,此属性才有意义。capture = 'user'调用前置摄像头capture = 'environment'调用后置摄像头- 不设置则为用户自己选择
multiple,布尔值。 是否可以选择多个文件
当点击选择文件按钮后,出现文件列表(pc端),选择好文件,就会触发input的change事件。(如果是已经选择好了文件,再次点击选择文件按钮,也会触发input的change事件,不选择文件关闭弹窗,e.target.files为空。),通过e.target.files获取文件。
async inputChange (e) {
console.log(e)
const file = e.target.files[0]; // e.target.files返回FileList,为文件列表,单文件上传取第0个
console.log(file);
}
拖拽文件上传
input标签也可以拖拽上传,和上面的上传流程一样,文件拖拽到input上,放下的时候会触发input的change事件。
这里使用div标签,监听div的drop事件,通过e.dataTransfer.files获取文件。
这里一定要把dragover和drop的默认事件阻止了,否则浏览器默认会打开或下载文件。
<div id="drap">
拖拽上传图片
</div>
let drapEl = document.querySelector('#drap')
drapEl.addEventListener('dragover', e => {
e.preventDefault()
}, false)
drapEl.addEventListener('drop', e => {
e.preventDefault()
console.log(e.dataTransfer.files)
}, false)
自定义类型和大小
文件类型限制
input的accept属性可以定义一些可接受的文件类型,但是在使用拖拽上传的时候,这个属性就没用了。一些别的类型的文件,还是会触发change事件,并且在e.target.files里能获取到。accept属性只是在点击选择文件按钮的时候,除规定类型,其他的类型不可见。使用我们一般都需要自己校验一下文件类型。
let file = e.target.files[0]
if (!file) return false // 如果文件不存在,就终止。这种情况发生在,已经选择了文件,再次点击选择文件按钮,但是没选择文件,点了取消按钮。
let reg = /(jpe?g|bmp|gif|png)$/;
if (!reg.test(file.type)) {
console.log('类型不符')
return //类型不符
}
图片大小限制
有时候我们也需要限制一下图片的大小,图片太大可能会上传不上去:
if (file.size > 1024*200) { // file.size是字节大小
console.log('图片不能大于200K')
return
}
图片像素大小限制
有时候我们也会对图片的像素大小做一些限制,譬如只能传大于等于100*100的图片:
let picInfo = {}
try {
picInfo = await checkPicture(file)
} catch (e) {
console.log(e)
}
if (!picInfo.width || !picInfo.height || picInfo.width < 100 || picInfo.height < 100) {
console.log('请上传指定大小文件')
return
}
function checkPicture (file) { // 这个函数返回一个promise
return new Promise((resolve, reject) => {
const reader = new FileReader() // FileReader对象
reader.readAsDataURL(file)
reader.onload = function (e) {
const data = e.target.result // result的类型取决于读取操作方法,readAsDataURL返回base64字符串
const image = new Image()
image.onload = function () {
const {width, height} = image
resolve({
width,
height
})
}
image.onerror = function (e) {
reject(e)
}
image.src= data
}
})
}
FileReader对象
FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
const reader = new FileReader()
属性:
FileReader.readyState的状态值(只读):
| 常量名 | 值 | 描述 |
|---|---|---|
| EMPTY | 0 | 还没有加载任何数据 |
| LOADING | 1 | 数据正在被加载 |
| DONE | 2 | 已完成全部的读取请求 |
FileReader.result文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。
方法:
FileReader.abort()。中止读取操作。在返回时,readyState属性为DONE。FileReader.readAsArrayBuffer()。开始读取指定的Blob中的内容, 一旦完成,result属性中保存的将是被读取文件的ArrayBuffer数据对象。FileReader.readAsDataURL()。开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的Base64字符串以表示所读取文件的内容。FileReader.readAsText()。开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
事件处理:
- FileReader.onabort。处理abort事件。该事件在读取操作被中断时触发。
- FileReader.onerror。处理error事件。该事件在读取操作发生错误时触发。
- FileReader.onload。处理load事件。该事件在读取操作完成时触发。
- FileReader.onloadstart。处理loadstart事件。该事件在读取操作开始时触发。
- FileReader.onloadend。处理loadend事件。该事件在读取操作结束时(要么成功,要么失败)触发。
- FileReader.onprogress。处理progress事件。该事件在读取Blob时触发。
我一般都是使用 onload和FileReader.readAsDataURL()处理图片。如上面checkPicture方法。
图片压缩
图片压缩可以使用canvas.toDataURL方法进行压缩,比较简单,需要注意的是图片跨域问题。
这里我用的是npm包image-conversion,使用起来非常方便,还可以压缩到指定大小。
import imageConversion from 'image-conversion'
let res // 需要一个变量存图片压缩后的blob对象
try {
res = await imageConversion.compressAccurately(file, 200) // 图片压缩,指定压缩到200K左右
res.name = file.name // 压缩后没name属性了,需要添加一个。如果页面上不需要展示name,则不必。
} catch (e) {
console.log(e)
}
if (res && res.size && res.size / 1024 > this.size && this.size !== 0) { // this.size是指定的图片大小。
return // 压缩后再次判断图片大小,超过大小则提示
} else if (!res && !res.size && file.size / 1024 > this.size && this.size !== 0) {
return // 压缩失败了也再次判断图片大小,超过大小则提示
}
this.file = (res && res.size) ? res : file // 如果压缩失败,就用没压缩的文件
image-conversion还有一些别的用法,参考image-conversion git地址
图片预览
FileReader读取完的图片,就可以预览了,可以在上面checkPicture方法里resolve(img),然后设置img标签的src就可以了。
FormData对象
FormData 接口提供了一种表示表单数据的键值对的构造方式,经过它的数据可以使用 XMLHttpRequest.send() 方法送出,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。
使用:
const formDate = new FormData() // new一个FormData实例
formDate.append('file', e.file) // 添加数据
// formDate.set('file', e.file)
formDate.append()向FormData中添加新的属性值,FormData对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值。formDate.set()给FormData设置属性值,如果FormData对应的属性值存在则覆盖原值,否则新增一项属性值。
我一般都用这2个方法就够了,其他的方法参考MDN
axios上传文件
需要设置 header 中的 Content-Type为 multipart/form-data
let config = {
headers: {
'Content-Type': 'multipart/form-data'
},
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
}
axios
.post('serverUrl', formDate, config)
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
最后
以上就是目前上传使用到的相关点。关于大文件上传和断点续传,可以参考字节跳动面试官:请你实现一个大文件上传和断点续传。