# 图片压缩优化

# 背景

控制器添加了很多向导功能,导致包大小从几十M膨胀到了两百多M,所以急需优化包体积。

# 优化方案

普通的文件,类似js、css,已在打包时进行压缩混淆了,没什么优化空间。

那就直接对图片进行压缩

# 方案一

通过sharp对图片压缩

# 思路

通过递归给定的目录,找出所有图片,并针对类型进行配置,通过sharp进行压缩。

需要注意的是,sharp不支持svg,需要另外使用imagemin-svgo处理

压缩完成后,直接覆盖源文件

const sharp = require('sharp');
const imagemin = require('imagemin');
const imageminSvgo = require('imagemin-svgo');

async function handleSvg(filePath) {
  const result = await imagemin([filePath], {
    destination: dir,
    plugins: [
      imageminSvgo({
        plugins: [
          { removeViewBox: false },
          { removeDimensions: true },
        ],
      }),
    ],
  });
  console.log(result);
}

async function compressImages(dir, compressedList) {
  const files = await readdir(dir);

  for (const file of files) {
    const filePath = path.join(dir, file);
    const stats = await stat(filePath);
    if (stats.isDirectory()) {
      await compressImages(filePath, compressedList);
    } else {
      const ext = path.extname(file).toLowerCase();
      if (!isImage(ext)) continue;
      // svg
      if (ext === '.svg') {
        handleSvg();
      } else {
        if (compressedList[file] && compressedList[file].size === stats.size) {
          // console.log(`文件 ${file} 已经压缩过,跳过处理`);
          continue;
        }
      }

      console.log(`文件 ${file} 压缩中...`);
      const startTime = new Date().getTime(); // 记录开始时间
      let _ext = ext.replace('.', '');
      if (_ext === 'jpg') {
        _ext = 'jpeg';
      }

      let options = {}; // sharp 配置
      let formatOptions = {}; // 不同格式方法参数

      // 根据文件格式, 设置不同的配置
      switch (_ext) {
        case 'gif':
          options = {
            animated: true,
            limitInputPixels: false,
          };
          formatOptions = { colours: 128 };
          break;
        default:
          formatOptions = { quality: 70 };
      }
      sharp.cache(false);
      if (_ext !== 'gif') {
        let image = sharp(filePath, options);
        const imageBuffer = await image[_ext](formatOptions).toBuffer();
        fs.writeFileSync(filePath, imageBuffer); // 覆盖源文件
      }
      const compressedSize = fs.statSync(filePath).size;
      compressedList[file] = { size: compressedSize, originalSize: stats.size, filePath }; // 记录压缩前后文件大小
      const endTime = new Date().getTime(); // 记录结束时间
      console.log(`文件 ${file} 压缩完成,耗时 ${endTime - startTime} 毫秒`);
    }
    // }
  }
}

# 缓存

每次压缩时,记录文件名及大小,下次再执行压缩时通过对比日志,对已压缩过的进行跳过,达到缓存的效果。

# 注意点

sharp包需要在.npmrc中配置镜像地址

# 缺点

脚本写在项目里,且sharp包十分难装,影响打包速度,还可能失败。

并且对gif的压缩效果不理想,压缩颜色范围可以减小体积,但是会导致颜色失真,影响效果

所以,该方案pass

# 方案二

最终选用的该方案

基于开源项目https://github.com/leibnizli/hummingbird 改造

该项目使用了sharp进行裁剪、imagemin进行压缩,可以对图片、视频等进行压缩

并且是通过electron打包成软件包,可安装使用,解决了imagemin包在内网安装难的问题

当然,对于gif来说,压缩效果同样不理想。所以对于gif我们另外处理。

# 改造点

由于该项目会对js、css压缩,这是我们不需要的,所以在代码中需屏蔽掉。

gif的压缩等级调到最大

# 流水线

之所以不在流水线中做压缩,原因有以下:

  1. imagemin包不好装
  2. 每次都压缩图片会拖慢打包速度

所以决定用单独的工具进行压缩

# 缓存

参照方案一的缓存方案,实现功能

# 安装

目前只提供windows版本

下载地址:

# 开发

# 依赖包

由于依赖十分难装,在内网装不上,有需要的可前往``下载

# 打包

打包需要用到的electron的三方包,需手动下载后放在电脑缓存中

# gif压缩

gif的压缩通常就以下几个方向:

  1. 尺寸
  2. 颜色范围
  3. 帧率

尺寸要适配弹窗,不能改

颜色范围降低会产生色差,也不能改

帧率,可以降到合理范围,但是目前所有三方包都不支持自动改帧率,所以决定使用screenToGif软件手动抽帧,实测帧率在5~8帧范围内,看起不影响效果。之前很多都是30帧,对于引导图来说,完全用不上。

# 优化效果

优化前:219M

优化后:127M

减小包体积42%