需求
在维护公司的公共组件库时,发现前辈留下来的图片压缩功能,所以我当即就试了一下,图片压缩是配合文件上传做的,实际上就是上传前对图片进行算法压缩,这样能极大地减少服务器开销,因为他在一些用户肉眼无法察觉的情况下压缩体积减小了90%左右!于是就来了兴趣开始研究起了代码
思路
这里说一下整个压缩的思路:
- 通过组件或者标签获取file文件
- 获取文件类型,通过不同类型进行不同处理
- 将file文件类型转换为Base64格式
- 通过canvas绘制上传图片,然后通过API进行压缩
- 将压缩好的Base64数据转换为Blob或者file格式(根据具体情况选择)
- 构建FormData数据,进行文件上传
其中有些步骤应该不需要介绍了,一些开源组件库里基本上有封装好的回调,下面介绍一下思路里的核心:
file文件转为Base64
这里根据浏览器提供的FileReader象去对file类型进行操作
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
...
}
readAsDataURL 方法会读取指定的 Blob 或 File对象。读取操作完成的时候,readyState会变成已完成DONE,并触发 loadend事件,同时result属性将包含一个data:URL 格式的字符串(base64 编码)以表示所读取文件的内容。
压缩
这里的压缩原理是通过canvas绘制出的图片然后再利用toDataURL,toBlob两个API去转换canvas上的数据,两个API的区别就在于前者是一个异步,后者是同步操作以及他们返回的数据类型不一样,他们的参数是一致的(这里以toBlob为例)
- callback ——异步结束后的回调,回调的默认参数是Blob
- type——文件类型
- quality——质量(压缩核心)
一般的图片质量是0.92,也是该值的默认值,值数大小在0-1之间,越小质量越小,体积也越小,但是基本上在0.2以上图片是能够躲过人眼肉眼的区分,如果需要缓存高清图片的时候,该值的区间应该在0.5-0.9区间,如果是一些非高清的图片,那么该值的区间可以在0.2-0.5之间
在压缩的过程中发现,该方法无法压缩PNG图片,原因好像是因为PNG图片使用的是采用无损压缩算法的位图格式,所以这里需要对PNG文件进行额外处理
//声明FileReader文件读取对象
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// 生成canvas画布
const canvas = document.createElement("canvas");
// 生成img
const img = document.createElement("img");
// 处理跨域,canvas中的image标签会出现跨域问题 https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
img.crossOrigin = "anonymous";
img.src = reader.result;
img.onload = () => {
const ctx = canvas.getContext("2d");
//原始图片宽度、高度
let originImageWidth = img.width,
originImageHeight = img.height;
//默认最大尺度的尺寸限制在(1920 * 1080)
let maxWidth = 1920,
maxHeight = 1080,
ratio = maxWidth / maxHeight;
//目标尺寸
let targetWidth = originImageWidth,
targetHeight = originImageHeight;
//当图片的宽度或者高度大于指定的最大宽度或者最大高度时,进行缩放图片
if (
originImageWidth > maxWidth ||
originImageHeight > maxHeight
) {
//超过最大宽高比例
if (originImageWidth / originImageHeight > ratio) {
//宽度取最大宽度值maxWidth,缩放高度
targetWidth = maxWidth;
targetHeight = Math.round(
maxWidth * (originImageHeight / originImageWidth)
);
} else {
//高度取最大高度值maxHeight,缩放宽度
targetHeight = maxHeight;
targetWidth = Math.round(
maxHeight * (originImageWidth / originImageHeight)
);
}
}
// canvas对图片进行缩放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除画布
ctx.clearRect(0, 0, targetWidth, targetHeight);
// 绘制图片
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
if (file.type == 'image/png') {
// 对png图片进行k-d tree算法压缩,这里使用的是upng-js算法
...
} else {
// 对jpg和jpeg使用常规的canvas压缩方法
...
}
}
}
关于图片压缩,一般常用的工具莫过于小熊猫了,但是小熊猫商用是收费的,所以这里我找了另外一个开源算法处理PNG图片——UPNG,压缩力度稍逊小熊猫,但无奈他是免费开源的,使用UPNG的encode对png文件进行压缩即可