canvas模糊的根本原因是未乘设备像素比(dpr):canvas.width/height设为375×600等固定值,忽略真机dpr=2或3,导致绘图缓冲区物理像素不足,系统强制插值拉伸,引发毛边、锯齿;正确做法是动态获取dpr,将canvas物理宽高设为逻辑尺寸×dpr,并调用ctx.scale(dpr,dpr)对齐坐标系,同时控制导出体积≤120kb。

canvas.width/height没乘dpr,图就注定糊
直接写死 canvas.width = 375、canvas.height = 600 是最常见错误。微信小程序 canvas 的 width 和 height 属性决定的是绘图缓冲区的物理像素数,不是 CSS 显示大小。iPhone 真机 pixelRatio 多为 2 或 3,不乘 dpr 就等于用 375×600 的画布去填满 750×1200 的物理区域,浏览器只能插值拉伸——结果就是毛边、发虚、文字锯齿。
正确做法是:
- 在
onReady中用uni.createSelectorQuery()拿到 canvas 节点 - 读取
uni.getSystemInfoSync().pixelRatio - 设
node.width = 逻辑宽度 × dpr、node.height = 逻辑高度 × dpr(注意:是 node 属性,不是 style) - 之后所有绘图坐标、字号、线宽都按逻辑尺寸写,不用再手动乘 dpr
ctx.scale(dpr, dpr)必须在draw前调用,且不能漏
ctx.scale(dpr, dpr) 不是“锦上添花”,而是把逻辑坐标系对齐物理像素的关键操作。它让 1 个逻辑单位正好占满 dpr×dpr 个物理像素,避免子像素渲染和插值模糊。
容易踩的坑:
- 只在初始化时调用一次,后续
clearRect()后没重设 —— 再次绘图就会错位模糊 - 在
drawImage()加载远程图后才调用,导致图片已按 1:1 渲染,再 scale 也救不回 - 用
type: '2d'创建 context,在 iOS 部分基础库版本会丢失上下文,建议显式传{ type: 'webgl' }
drawImage 加远程图必先下载,否则跨域+降采样双杀
直接 ctx.drawImage('https://xxx.png', ...) 在真机上大概率失败:一是 CORS 报错白屏,二是 iOS WebView 对 HTTPS 图片自动降采样,原始像素全丢。
可靠路径是:
- 先用
uni.downloadFile下载到本地临时路径 - 再用
uni.getImageInfo获取真实宽高(含 orientation 修正) - 根据逻辑尺寸计算缩放比:
scaleX = 逻辑宽 / imageInfo.width,然后传给drawImage - 若图本身只有 1x 分辨率(比如没提供 @2x),再怎么适配也糊——确保后端返回的是匹配 dpr 的图源
分享图体积超 120KB,微信会强制二次压缩
即使 canvas 绘制完全高清,导出的临时文件路径传给 uni.share 后,微信仍可能把它压成马赛克。实测阈值就在 120KB 左右。这不是前端能绕过的限制,必须主动控体积。
压缩建议:
- 调用
canvas.toTempFilePath时显式传quality: 0.8(默认是 1) - 导出尺寸别盲目拉高,例如 5:4 封面,750rpx 宽足够,对应物理宽建议 ≤ 1500px(dpr=2 时)
- 避免在 canvas 上绘制大量阴影、模糊滤镜,这些极大增加文件体积
- 文字优先用系统字体(如
'sans-serif'),少用自定义字体文件
真正难的不是“怎么画清楚”,而是“怎么让微信不把它变糊”——dpr 适配只是第一步,体积控制才是临门一脚,漏掉任意一环,真机上看到的还是那张糊图。











