背景
最近在项目中遇到需要对用户上传图片进行压缩的场景,本地图片动辄1-2M,大的甚至10多M。即使将图片大小限制在5M内,在移动端反显也会出现卡顿延迟。因此需要用户上传图片后,由前端压缩图片大小,再上传到服务器,这样可以减少移动端上行流量,减少用户上传等待时长,优化用户体验。
解决方案
原理:
canvas是H5的重要属性,允许我们在网页上创建一个可绘制的区域,在图片处理中,主要使用2D上下文,它提供了图像绘制和处理的功能,将图片进行压缩并转换为Base64格式。
step1:首先通过上传组件获取用户上传的图片,仅支持jpg,png格式,最大限制为5M
step2:将图片加载到canvas,使用FileReader对象将用户上传的图片读取为DataURL,使得我们可以访问和处理图片的像素数据。其中入参为图片DataURL,压缩质量quality(模糊度越接近1,与原图效果更贴近),最大尺寸限制maxSize,回调函数callback。
step3:设置目标尺寸,为了减少图片的物理尺寸,设定目标尺寸来限制图片压缩后的宽度和高度。
step4:绘制图片到canvas画布上。使用canvas上下文中的drawImage方法,将原始图片绘制到画布上,并同时改变其尺寸。(等于将图片进行重绘)
step5:使用递归算法,将图片进行反复压缩直到达成预期效果。循环条件为base64.length * 0.75 > maxSize,quality -= 0.02,质量每次递减0.02,以寻找满足要求的最佳临界点。
step6:转换为Base64格式,最后使用Canvas的toDataURL方法将画布上的内容转换为base64格式图片,base64图片设置为压缩后的<img>标签的src属性,以便在页面上显示压缩后的图片供用户查看。
代码实践
//图片压缩入口
photoCompress(file,callback){
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = ()=>{
if(file.size > this.maxSize){
this.canvasDataURL(reader.result,0.92,this.maxSize,callback)
}else{
callback(reader.result)
}
}
}
//图片压缩
canvasDataURL(path, quality, maxSize, callback){
const img = new Image()
img.src = path
img.onerror = (err) => {
callback(path)
}
img.onload = () => {
const w = img.width
const h = img.heigth
// 生成canvas
const convas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
//canvas节点属性设置
const anw = document.createAttribute('width')
const anh = document.createAttribute('heigth')
anw.value = w
anh.value = h
canvas.setAttributeNode(anw)
canvas.setAttributeNode(anh)
// 画图
ctx.drawImage(img, 0, 0, w, h)
// 压缩
let base64 = canvas.toDataURL('image/jpeg',quality)
while(base64.length * 0.75 > maxSize){
quality -= 0.02
base64 = canvas.toDataURL('image/jpeg',quality)
}
callback(base64)
}
}
// 方法调用
this.photoCompress(file.raw, (base64) => {
this.myFrom.storyImgStr = base64; // 设置img图片的src属性值
this.$refs.myForm.validateField("storyImgStr") // 表单校验
}
)
总结
最终在不影响图片展示效果的情况下,5M 大小的图片能优化到 500k,对于用户上传的不可控图片是个不错的压缩方法。图片性能优化一直都是个老生常谈的问题,总结归纳项目中遇到的真实场景,希望能给大家提供新的思路与解决方法。