Skip to content

微信小程序图片压缩的四次迭代

背景

儿童任务积分小程序里,家长和孩子需要拍任务凭证上传。测试反馈"照片太糊,人像看不清"。排查后发现是整个管线的问题——微信端、Nginx 层、后端压缩层在互相伤害。

初始状态:三层压缩打架

chooseImage({ sizeType: ['compressed'] })  →  上传  →  sharp 压缩  →  OSS
  • 微信 compressed:5-6MB 原图被黑盒压缩,不可控
  • Nginx:默认 client_max_body_size 1MB,大图 413 直接拒
  • 后端 sharp:超 1MB 就 2048px + quality 75,还超就 1280px + quality 60

最终产物:1440×1080、328KB,人像像打了马赛克。

第一轮:提高阈值 + 渐进式压缩

修了两个明显问题:

Nginxkidapi.xidou.topclient_max_body_size 15m

sharp — 一刀切改成 7 档渐进式,从高画质往低试,第一个 ≤2MB 的就是结果:

javascript
const presets = [
  { width: 2048, quality: 92 },
  { width: 2048, quality: 82 },
  { width: 1600, quality: 85 },
  { width: 1440, quality: 80 },
  { width: 1280, quality: 78 },
  { width: 1080, quality: 75 },
  { width: 800,  quality: 72 },
]
// 启用 mozjpeg + progressive 编码器

效果:6.65MB(4096×3072) → 2048×1536、2.07MB、quality 92。清晰度好了,但 6.65MB 原图上传 4G 下要 3-5 秒,体验差。

第二轮:前端 Canvas 重绘(翻车)

以前用过这套——选原图 → canvas drawImage 缩小 → toTempFilePath 导出。

结果:连踩三个坑。

  1. PNG 透明通道变纯黑/纯蓝 — canvas 没填白底,透明区域渲染成随机色
  2. 安卓兼容性 — 部分机型 toTempFilePath 的 quality 参数不生效
  3. canvas-id 已废弃 — 新版基础库用 type="2d",异步时序各机型不一致

直接放弃 canvas 方案。

第三轮:发现 wx.compressImage 的隐藏参数

查微信文档发现,从基础库 2.26.0 开始,wx.compressImage 支持 compressedWidth / compressedHeight 参数——可以精确控制输出尺寸。这和 chooseImagecompressed 黑盒完全不是一回事。

第四轮:wx.compressImage + sharp 兜底(最终方案)

javascript
// uni-app 封装
function nativeCompress(srcPath) {
  return new Promise((resolve) => {
    uni.getImageInfo({
      src: srcPath,
      success: (info) => {
        const MAX = 1920
        if (info.width <= MAX && info.height <= MAX) { resolve(srcPath); return }
        uni.compressImage({
          src: srcPath,
          quality: 92,
          ...(info.width >= info.height
            ? { compressedWidth: MAX }
            : { compressedHeight: MAX }),
          success: (r) => resolve(r.tempFilePath),
          fail: () => resolve(srcPath)  // 失败兜底原图
        })
      },
      fail: () => resolve(srcPath)
    })
  })
}

最终管线:

选原图 → wx.compressImage(1920px, q92) → 上传(400-600KB) → sharp 兜底(几乎不触发) → OSS

实测效果:6.65MB 原图 → 前端压到 ~500KB → 1-2 秒上传完成 → sharp 无需再动。

顺便修的其他坑

原因修复
上传报"服务器响应异常"Nginx 默认 body 1MBclient_max_body_size 15m
Fastify 收不到文件multipart 限制 5MB提到 10MB
v2 接口 404路径拼成 /api/v1/v2/...v2 用独立 BASE_URL

总结

图片压缩不是算法问题,是管线问题。三层各做各的、各司其职:

  • 前端压缩尺寸,降上传体积 — 用户无感
  • 传输层放开限制 — 不挡正常请求
  • 后端兜底微调 — 保底质量

另外牢记:别在小程序里用 canvas 压缩图片。微信原生 wx.compressImage 更简单、更稳。