使用UPNG算法和canvas压缩对图片进行前端压缩

334 阅读4分钟

需求

在维护公司的公共组件库时,发现前辈留下来的图片压缩功能,所以我当即就试了一下,图片压缩是配合文件上传做的,实际上就是上传前对图片进行算法压缩,这样能极大地减少服务器开销,因为他在一些用户肉眼无法察觉的情况下压缩体积减小了90%左右!于是就来了兴趣开始研究起了代码

思路

这里说一下整个压缩的思路:

  1. 通过组件或者标签获取file文件
  2. 获取文件类型,通过不同类型进行不同处理
  3. 将file文件类型转换为Base64格式
  4. 通过canvas绘制上传图片,然后通过API进行压缩
  5. 将压缩好的Base64数据转换为Blob或者file格式(根据具体情况选择)
  6. 构建FormData数据,进行文件上传

其中有些步骤应该不需要介绍了,一些开源组件库里基本上有封装好的回调,下面介绍一下思路里的核心:

file文件转为Base64

这里根据浏览器提供的FileReader象去对file类型进行操作

const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
    ...
}

readAsDataURL 方法会读取指定的 BlobFile对象。读取操作完成的时候,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文件进行压缩即可

PS:很久没有更新了,之前都比较忙,接下来的几个月要开始准备春招了,所以可能会比较勤奋更新文章