别用 uni.compressimage 做主压缩逻辑——它在 h5 不存在、小程序是空函数、app 端 quality 参数表示最大边长而非质量值;稳定压至 ≤200kb 且肉眼难辨失真的方案只能是 canvas 手动缩放 + dpr 适配 + jpeg 强制转码。

别用 uni.compressImage 做主压缩逻辑——它在 H5 不存在、小程序是空函数、App 端 quality 参数实际表示最大边长而非质量值,跨端行为完全不可控;真正能稳定压到 ≤200KB 且肉眼难辨失真的方案,只有 Canvas 手动缩放 + DPR 适配 + JPEG 强制转码。
为什么 uni.compressImage 压完还超大、发糊、丢透明
这个 API 名字极具误导性:
- iOS:传
quality: 80只影响 JPEG 量化表,PNG 不生效;原图任一边 > 4096px 会静默跳过压缩,直接返回原图路径 - Android:很多厂商忽略
quality,按固定比例缩放,甚至出现色偏、发紫 - 微信小程序:
sizeType: ["compressed"]常返回和原图一样大的“假压缩”文件;PNG 强制转 JPEG,透明通道直接丢失 - H5:
uni.compressImage根本未定义,调用即报undefined is not a function -
compressedWidth/compressedHeight是最大允许值,不是目标尺寸,也不保证等比
Canvas 压缩必须按 pixelRatio 设物理像素才不糊
在 iPhone 14 Pro(DPR=3)上画一个 CSS 宽 800px 的 canvas,若不处理 DPR,浏览器会插值放大到 2400 物理像素,必然模糊。关键不是“画多大”,而是“物理像素画多少、CSS 尺寸设多少、drawImage 传什么”:
- 先调
uni.getSystemInfoSync().pixelRatio拿当前设备 DPR - 创建 canvas 时,
width/height设为「目标 CSS 尺寸 × DPR」,如目标输出 800×600 → canvas 设 2400×1800 - 同时设置
canvas.style.width = "800px"、canvas.style.height = "600px" -
ctx.drawImage(img, 0, 0, 800, 600)—— 这里传的是 CSS 尺寸(800/600),不是 canvas 物理尺寸 -
uni.canvasToTempFilePath的destWidth/destHeight必须填 800/600,否则输出图会被二次缩放
base64 直传 uni.uploadFile 会让体积增大 30%
很多项目用了 uni-image-compress 后上传反而更大,根本原因是把 base64 字符串当 filePath 直接塞进 uni.uploadFile:
-
uni.uploadFile({ filePath: "data:image/jpeg;base64,..." })会触发额外 base64 编码开销,体积膨胀约 30% - 正确做法:确保插件或自写逻辑最终生成的是临时文件路径,如
uni-file://xxx.jpg或__APP__/xxx.jpg - 若必须走 base64(如 H5),则不能用
filePath,得改用formData方式上传,并手动设header: {"Content-Type": "image/jpeg"} - 校验压缩结果:用
atob粗略估算 base64 字节数 ——Math.ceil(base64.length * 3 / 4) - (base64.indexOf('==') > 0 ? 2 : base64.indexOf('=') > 0 ? 1 : 0),避免上传超限(如微信 5MB 限制)
最易被忽略的点:DPR 不是常量,每台 iPhone 都不同;MIME 类型不能信后缀(getImageInfo 返回 type: "webp" 但服务端不认);“不失真”必须真机放大看文字边缘与灰阶过渡——写对 canvas 尺寸只是第一步,漏掉任何一个环节,上线后就是糊图、灰图、上传卡死。











