微信小程序图片压缩的四次迭代
背景
儿童任务积分小程序里,家长和孩子需要拍任务凭证上传。测试反馈"照片太糊,人像看不清"。排查后发现是整个管线的问题——微信端、Nginx 层、后端压缩层在互相伤害。
初始状态:三层压缩打架
chooseImage({ sizeType: ['compressed'] }) → 上传 → sharp 压缩 → OSS- 微信 compressed:5-6MB 原图被黑盒压缩,不可控
- Nginx:默认
client_max_body_size1MB,大图 413 直接拒 - 后端 sharp:超 1MB 就 2048px + quality 75,还超就 1280px + quality 60
最终产物:1440×1080、328KB,人像像打了马赛克。
第一轮:提高阈值 + 渐进式压缩
修了两个明显问题:
Nginx — kidapi.xidou.top 加 client_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 导出。
结果:连踩三个坑。
- PNG 透明通道变纯黑/纯蓝 — canvas 没填白底,透明区域渲染成随机色
- 安卓兼容性 — 部分机型
toTempFilePath的 quality 参数不生效 - canvas-id 已废弃 — 新版基础库用
type="2d",异步时序各机型不一致
直接放弃 canvas 方案。
第三轮:发现 wx.compressImage 的隐藏参数
查微信文档发现,从基础库 2.26.0 开始,wx.compressImage 支持 compressedWidth / compressedHeight 参数——可以精确控制输出尺寸。这和 chooseImage 的 compressed 黑盒完全不是一回事。
第四轮: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 1MB | client_max_body_size 15m |
| Fastify 收不到文件 | multipart 限制 5MB | 提到 10MB |
| v2 接口 404 | 路径拼成 /api/v1/v2/... | v2 用独立 BASE_URL |
总结
图片压缩不是算法问题,是管线问题。三层各做各的、各司其职:
- 前端压缩尺寸,降上传体积 — 用户无感
- 传输层放开限制 — 不挡正常请求
- 后端兜底微调 — 保底质量
另外牢记:别在小程序里用 canvas 压缩图片。微信原生 wx.compressImage 更简单、更稳。
