Canvas画海报需确保“画得清、导得准、存得稳”:动态设置width/height为逻辑尺寸×dpr,绘图前调用ctx.scale(dpr,dpr),网络图片须先下载再校验,导出时destWidth/destHeight须匹配实际像素。
canvas 在 uni-app 里画海报,不是“能画出来就行”,而是“画得清、导得准、存得稳”。只要文字发虚、二维码扫不出、图片糊成一片,基本就是没处理 devicepixelratio 或没走对资源加载路径。
canvas 元素的 width/height 属性必须动态设为逻辑尺寸 × dpr
很多人只改了 CSS 的 width 和 height,比如写成 style="width: 750px; height: 1334px;",这只能控制显示大小,不影响实际绘图缓冲区分辨率。结果就是:你画的是 750×1334 的逻辑区域,但 canvas 内部还是默认 300×225(或设备未缩放的低分辨率),绘图全被压缩拉伸。
- 先用
uni.getSystemInfoSync().pixelRatio拿到当前设备 dpr(iOS 通常是 2 或 3,安卓中高端机也普遍 ≥2) - 在
onReady中用uni.createSelectorQuery()获取 canvas 节点,然后直接赋值:node.width = 750 * dpr、node.height = 1334 * dpr - 注意:这是 DOM 属性赋值,不是 style;
node.setAttribute('width', ...)也行,但直接点属性更稳妥 - H5 环境下如果用
document.createElement('canvas'),同样要手动设.width和.height,不能只靠 CSS
ctx.scale(dpr, dpr) 必须在 drawImage / fillText 前调用,且每次重绘都要重设
ctx.scale() 是把整个坐标系放大,让 1 单位逻辑像素对应 dpr×dpr 物理像素。但它不持久——每次 ctx.clearRect() 或新帧绘制后,缩放状态就丢了。
- 必须在所有绘图操作前调用:
ctx.scale(dpr, dpr) - 如果你用
ctx.save()/ctx.restore()包裹某段绘制(比如水印),缩放也要在里面重设,否则 restore 后会回到原始缩放 - 字号、线宽、坐标全部按逻辑尺寸写(比如
ctx.font = '32px sans-serif'),不用乘 dpr;scale 已帮你做了映射 - 漏掉这步,哪怕 canvas 像素设对了,文字和线条依然毛边
网络图片必须先下载再 drawImage,不能直接传 URL
直接 ctx.drawImage('https://xxx.png', ...) 在小程序里大概率失败:一是跨域拦截导致空白,二是 iOS WebView 对远程图做降采样,导出后模糊加倍。
- 正确路径是:
uni.downloadFile()→ 拿到tempFilePath→uni.getImageInfo({ src: tempFilePath })校验宽高和 orientation(尤其竖拍图) - 不要跳过
getImageInfo:它能修正 EXIF 旋转,避免二维码或头像倒置 - 本地静态资源(如
/static/bg.png)可直传,但也要注意是否提供 @2x/@3x 版本;单张 1x 图放大后必然糊 - 二维码若由 base64 生成,需先写入本地文件(
uni.getFileSystemManager().writeFile),再用路径加载
uni.canvasToTempFilePath 导出时 destWidth/destHeight 要匹配 canvas 实际像素
这个 API 的 destWidth 和 destHeight 参数,是最终图片的像素尺寸。如果设成逻辑尺寸(比如 750×1334),而 canvas 实际是 1500×2668(dpr=2),导出图就会被强行压缩,细节丢失。
- 务必设为 canvas 节点的真实像素值:
destWidth: 750 * dpr、destHeight: 1334 * dpr - 不设这两个参数时,H5 下可能默认导出 CSS 尺寸(即模糊版),小程序端行为也不一致,显式声明最安全
- 导出前确保
ctx.draw(false, callback)的false参数已传(跳过延迟),否则可能截到空白帧 - 真机调试时,
canvasToTempFilePath失败常因路径非法或 canvas 为空;加个setTimeout(..., 100)给绘制留出微小余量,比盲目延 2s 更可靠
最易被忽略的其实是“重绘时机”:响应式海报尺寸变化、用户切换横竖屏、甚至某些安卓 WebView 触发 resize 事件后,canvas 像素尺寸和 ctx 缩放都得重新同步,否则下一帧就糊。这不是一次初始化就能一劳永逸的事。


















