核心是四个硬性约束:canvas-id必须显式唯一声明,网络图须先getImageInfo再drawImage,ctx.draw()必须用回调确保完成,保存必须用uni.canvasToTempFilePath而非toDataURL。

uni-app 里用 Canvas 绘制海报,核心不是“会不会画”,而是“怎么让图不丢、不空、不糊、不报错”。几乎所有失败都卡在四个硬性约束上:canvas-id 声明、网络图加载方式、draw() 同步时机、保存路径生成逻辑。
canvas-id 必须显式声明且全局唯一
不设 canvas-id,uni.createCanvasContext('xxx') 直接报错 “canvas-id attribute is undefined”;设了但重复(比如两个组件都用 "posterCanvas"),后续 uni.canvasToTempFilePath 返回空图或 "fail canvas is empty"——这不是警告,是运行时拦截。
-
canvas-id是属性名,不是id:<canvas id="xxx"></canvas>无效,必须写<canvas canvas-id="xxx"></canvas> - 多个海报组件共存时,每个
canvas-id要不同,例如"posterCanvas1"、"qrcodeCanvas",不能靠 class 或 ref 区分 - H5 端若误加
type="2d",整个 canvas 渲染失效,必须删掉type属性,用默认渲染模式
网络图片必须先 uni.getImageInfo 再绘制
直接把 https://xxx.png 传给 ctx.drawImage(),iOS 和多数安卓机上会静默失败:图不显示、控制台无报错、海报缺一块。这是因为 Canvas 的 drawImage 不支持跨域网络图直绘。
- 必须先调
uni.getImageInfo({ src: 'https://...' }),拿到res.path(本地临时路径)再传给drawImage - 如果
getImageInfo报 403/404,检查小程序后台是否已将该域名加入「downloadFile 合法域名」列表 - 不要用
uni.downloadFile+tempFilePath组合:iOS 下返回的路径有时无法被drawImage识别,getImageInfo兼容性更稳
ctx.draw() 必须带回调且等它完成才能保存
ctx.draw() 是异步操作,不加回调就立刻调 uni.canvasToTempFilePath,结果一定是空图或旧图。这不是渲染延迟,是 Canvas 渲染管线未 flush。
- 必须写
ctx.draw(true, () => { /* 保存逻辑 */ }),true表示清空之前所有绘制指令,避免残留 - 别在
draw外层套setTimeout模拟等待——不可靠,不同设备帧率差异大 - 如果海报含文字,确保
ctx.font已设置(如ctx.font = '14px sans-serif'),否则文字可能不渲染
保存必须用 uni.canvasToTempFilePath,不能用 toDataURL
ctx.toDataURL() 在 uni-app 中无法直接用于保存相册:H5 端不支持 uni.saveImageToPhotosAlbum,App 端和小程序端也只认本地临时路径(tempFilePath),base64 字符串会被忽略。
- 必须用
uni.canvasToTempFilePath({ canvasId: 'xxx', success: ... })生成真实路径 - 小程序端保存前需检查权限:
uni.getSetting→ 若scope.writePhotosAlbum === false,则调uni.openSetting()弹系统设置页,不能反复authorize - H5 和 App 端要条件编译:
// #ifdef MP-WEIXIN包裹保存逻辑,// #ifndef MP-WEIXIN分支做降级(如提示“长按保存”或生成 blob 下载链接)
最容易被忽略的是:canvas-id 大小写敏感、getImageInfo 的域名白名单、draw 回调的强制依赖——这三个点任何一个没对齐,海报就无声无息地“消失”了,连错误日志都不抛。别猜,先查这三项。











