为什么要前端来压缩图片
- 因为现在h5手机拍照功能强大,导致图片动辄5,6M,如果这样子直接上传的话,不做限制,会导致流量庞大,服务器请求过慢
- 服务端的上传图片是有要求图片大小,而且直接传这么大图片,带宽它也受不了,所以前端进行压缩图片就成了一个必要的环节
压缩流程
- 通过input标签拿到需要的上传图片文件
- 将拿到的图片文件对象转成img标签
- 在canvas上压缩绘制成HTMLImageElement
- 将canvas绘制的图像转成blob文件
- 将blob文件转成file对象文件(根据后台接口选择需不需要)
- 将blob文件或者file对象文件上传到服务器
1.通过input标签拿到文件
这一步不需要多说,毕竟都很熟悉了吧。。。通过原生的input标签,设置type熟悉为file,然后再设置accept属性,来限制选择的文件类型,比如现在要拿到图片,就设置为image/*,accept具体属性配置点击这里,然后绑定onChange事件,来获取选择后的文件
<input type="file" accept="image/*" />
点击选择文件,获取值,得到的是file对象
2.将拿到的图片文件对象转成img对象
拿到图片文件后,先将其转成HTMLImageElement,也就是普通的img标签,具体要使用 FileReader构造函数。
先new出来一个img和fileReader的实例,通过fileReader的 readAsDataURL这个api,来读取图片文件,其返回值是一个编码后的base64的字符串,然后将这个字符串赋值给img的src属性上,这样就完成了图片文件到 HTMLImageElement的转化。
// 先new一个img和fileReader的实例
const img = new Image()
const reader = new FileReader()// 读取文件资源
reader.readAsDataURL(file)
reader.onload = function(e){
img.src = e.target.result
}
3.在canvas上压缩绘制成HTMLImageElement
拿到转化后的img元素后,先取出该元素的宽高度,这个宽高度就是实际图片文件的宽高度。
const {width: originWidth, height: originHeight} = img
然后定义一个最大限度的宽高度,如果超过这个限制宽高度,则进行等比例的缩放
// 最大尺寸限制
const maxWidth = 600,maxHeihgt = 600
// 需要压缩的目标尺寸
let targetWidth = originWidth, targetHeight = originHeight
// 等比例计算超过最大限制时缩放后的图片尺寸
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > 1) {
// 宽图片
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
// 高图片
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
计算好需要的尺寸后,创建一个canvas实例对象,设置canvas为压缩后的尺寸,并把img绘制在canvas上
// 创建画布
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 设置宽高度为等同于要压缩图片的尺寸
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
//将img绘制到画布上
context.drawImage(img, 0, 0, targetWidth, targetHeight)
4.将canvas绘制的图像转成blob文件
canvas绘制完成后,就可以使用canvas内置方法toBlob(),将绘制的图像转成blob文件了
canvas.toBlob(function(blob) {
resolve(blob)
}, type || 'image/png')
- 第一个参数 callback 回调函数,可获得一个单独的
Blob对象参数。 - 第二个参数 type
string类型,指定图片格式,默认格式为image/png。可选
5.将blob文件转成file对象文件(根据后台接口选择需不需要)
将转成blob文件再转化成file文件
const newFile = new File([blob], 'test.png', {
type: type || 'image/png'
})
- 第一个参数 bits 一个包含
ArrayBuffer,ArrayBufferView,Blob,或者DOMString对象的Array— 或者任何这些对象的组合。这是 UTF-8 编码的文件内容 - 第二个参数 name 表示文件名称,或者文件路径。
6.将blob文件或者file对象文件上传到服务器。
完整代码
// 压缩前将file转换成img对象
readImg(file) {
return new Promise((resolve, reject) => {
const img = new Image()
const reader = new FileReader()
reader.onload = function(e) {
img.src = e.target.result
}
reader.onerror = function(e) {
reject(e)
}
reader.readAsDataURL(file)
img.onload = function() {
resolve(img)
}
img.onerror = function(e) {
reject(e)
}
})
},
/**
* 压缩图片
*@param img 被压缩的img对象
* @param type 压缩后转换的文件类型
* @param mx 触发压缩的图片最大宽度限制
* @param mh 触发压缩的图片最大高度限制
*/
compressImg(img, type, mx, mh) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const { width: originWidth, height: originHeight } = img
// 最大尺寸限制
const maxWidth = mx
const maxHeight = mh
// 目标尺寸
let targetWidth = originWidth
let targetHeight = originHeight
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > 1) {
// 宽图片
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
// 高图片
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
// 图片绘制
context.drawImage(img, 0, 0, targetWidth, targetHeight)
canvas.toBlob(function(blob) {
resolve(blob)
}, type || 'image/png')
})
},
由于过程中存在异步操作,所以需要用到promise或者async await
我这是使用的是async await
async upload(file) {
const oFile = file.file
const img = await this.readImg(oFile)
const blob = await this.compressImg(img, oFile.type, 1024, 1024)
const newFile = new File([blob], oFile['name'], {
type: oFile.type
})
var formData = new FormData()
formData.append('file', newFile)
// 这是自己的api方法
uploadFile(formData).then(res => {
// 上传完之后的操作
})
},