工作总结(11) - 图片压缩

这篇工作总结将整理一下如何实现图片压缩。

之所以需要处理图片压缩,是因为现在很多时候从用户那获得图片并上传,其实对图片的质量要求大部分情况下都不高,而各个客户端的硬件能力也比较强,用户能提供比较高质量的图片,但对要求不高的服务端来说却是一种压力,所以在处理上传图片之前,如果能对图片做一步压缩可以减轻用户的流量负担,也可以减轻服务端的压力。

因为从用户那获得的是一个file对象,所以压缩的对象也是file对象。然后压缩可以通过图片的清晰度,尺寸来压缩。最后,因为图片压缩是一个异步处理,需要有一个回调函数来处理,当然,后面可以改成promise的方式。所以实现图片压缩方法,需要传入的参数就是这四个fileoption(包含qualityscale)和callback

1
2
3
const photoCompress = function (file, options, callback){
// ...
}

首先,file对象需要转成data:URL格式的Base64字符串,这样图片才能被canvas渲染到画布上。这里用到FileReader对象。它能让Web应用程序异步读取存储在用户计算机上的文件,得到几种格式的文件内容。这里用到FileReader.readAsDataURL()来转格式,然后需要调用FileReader.onload在读取操作完成后执行之后的操作

1
2
3
4
5
6
7
8
9
10
11
const photoCompress = function (file, options, callback){
let ready = new FileReader()
/*调用ready.readAsDataURL()来读取指定的Blob对象或File对象中的内容。当读取操作完成时,readyState属性的值会成为DONE,如果设置了onload事件处理程序,则调用之.同时,result属性中将包含一个data:URL格式的字符串以表示所读取文件的内容.*/
ready.readAsDataURL(file)
ready.onload=function(){
// 获取读取后的文件内容
let re = this.result
// 进行下一步的处理
canvasDataURL(re, options, callback)
}
}

获得文件内容之后,就可以来生成一个canvas画布,渲染图片了。先把图片文件加载好,然后在生成canvas标签,宽高直接取图片的宽高,然后生成画布,再把图片渲染到画布上,最后再获得压缩后的图片内容。尺寸的压缩可以在取宽高之后直接使用,得到压缩尺寸后的宽高,而清晰度则在最后获取压缩图片的时候调用canvas.toDataURL()时传入。这一步可以封装一个canvasDataURL()来实现,传入文件内容pathoption(包含qualityscale)和callback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const canvasDataURL = function (path, options, callback){
let img = new Image()
img.src = path
img.onload = function () {
let that = this
let imgScale = options.scale && options.scale <= 1 && options.scale > 0 ? options.scale : 1
// 默认按比例压缩
let w = that.width * imgScale
let h = that.height * imgScale
let scale = w / h
w = options.width || w
h = options.height || (w / scale)
let quality = options.quality && options.quality <= 1 && options.quality > 0 ? options.quality : 0.7 // 默认图片质量为0.7
//生成canvas
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
// 创建属性节点
let anw = document.createAttribute("width")
anw.nodeValue = w
let anh = document.createAttribute("height")
anh.nodeValue = h
canvas.setAttributeNode(anw)
canvas.setAttributeNode(anh)
ctx.drawImage(that, 0, 0, w, h)
// quality值越小,所绘制出的图像越模糊
let base64 = canvas.toDataURL('image/jpeg', quality)
// 回调函数返回base64的值
callback(base64)
}
}

这样就可以通过callback获得压缩后的data URI。但是这样的内容还需要转成file对象,才能用做上传或者其他用。

所以,在调用photoCompress方法时传入的callback中,需要做一步转化。首先要先将data URI中的base64编码截出来,转成blob对象,然后在用blob对象转成file对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将base64编码转成blob对象
const ConvertBase64UrlToBlob = (urlData) => {
// 分离类型跟base64 data
let arr = urlData.split(',')
// 分离出图片类型
let mime = arr[0].match(/:(.*?);/)[1]
let bstr = atob(arr[1])
let n = bstr.length
let u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], {type: mime})
}

为了外部调用使用时是处理同步状态,所以在调用photoCompress方法时会用一个函数包装,利用promise来处理。

1
2
3
4
5
6
7
8
9
10
11
12
compress (file) {
return new Promise((resolve, reject) => {
photoCompress(file, {
quality: 0.6,
scale: 1
}, (dataUrl) => {
let item = ConvertBase64UrlToBlob(dataUrl);
let newFile = new File([item], file.name, {type: item.type});
resolve(newFile);
})
})
}

这样调用的时候,就可以通过then或者async await来拿到压缩后的file文件。