因为ctx.draw()是异步提交渲染指令,未完成时调用uni.canvasToTempFilePath会读取空白位图;必须在draw(false, callback)回调中执行导出,确保位图数据就绪。

为什么draw()后立刻canvasToTempFilePath会拿到空白图
因为 context.draw() 是异步提交渲染指令,不是“画完就完事”。它只把绘图命令发给底层渲染管线,真正写入离屏位图(offscreen bitmap)需要时间。而 uni.canvasToTempFilePath() 读取的是当前已提交的位图数据——如果 draw 还没完成,位图就是空的。
这个现象在 iOS 真机上尤其明显,开发者工具常因模拟环境宽松而掩盖问题。
- 错误写法:
ctx.drawImage(...); ctx.draw(); uni.canvasToTempFilePath(...)—— 没等 draw 完就去取图 - 正确做法:必须用
draw(false, callback)的回调,确保绘制已同步到位图 - 注意:callback 触发 ≠ GPU 渲染完成,但 ≈ 位图数据已就绪,可安全导出
如何确保多张图片按顺序绘制不乱序
Canvas 绘制本身是顺序执行的,但图片加载(uni.getImageInfo 或 new Image())是异步的。如果直接循环调用 drawImage,实际执行顺序取决于哪张图先 onload,导致层叠错位、水印盖不住主体等。
核心是“资源就绪后再统一绘制”,而不是“边加载边画”。
- 用
Promise.all(图片加载 Promise 数组)预加载所有图片,拿到宽高和临时路径再开始绘图 - 避免在
img.onload里单独调用drawImage,否则时序不可控 - 所有
drawImage调用完成后,再统一调一次ctx.draw(false, callback) - 若需动态计算坐标(比如叠加水印),务必用加载后的实际
width/height,而非原始 URL 尺寸
canvas.width/height 设置不对也会导致内容“消失”
不是真消失,而是被缩放拉伸到看不见——这是小程序 Canvas 最隐蔽的坑。WXML 中的 style="width: 375px; height: 667px" 只控制显示大小,而 canvas.width 和 canvas.height 才决定绘图表面的物理像素网格。两者不匹配时,浏览器自动插值缩放,内容可能被压缩成一条线或放大成马赛克。
- 必须显式设置 canvas 实例的
width和height属性,值 = CSS 尺寸 ×uni.getSystemInfoSync().pixelRatio - 例如:设计稿宽 375px,dpr=2,则 canvas.width 应设为 750;否则 1px 线条会被拉伸成模糊的 2px 带
- uni-app 中不能直接操作 DOM canvas 元素,需通过
uni.createSelectorQuery()获取节点后再赋值 - 注意:
canvas.width = xxx会清空当前绘图状态,务必在获取 context 后、绘制前设置
跨域图片导致 canvas “受污染”无法导出
只要用 drawImage 绘制了跨域图片(比如 CDN 上的头像、海报底图),canvas 就会被标记为“tainted”,此时 uni.canvasToTempFilePath() 会静默失败或返回空白,控制台通常无报错。
这不是 uni-app 的 bug,是浏览器/小程序的安全策略强制行为。
- 检查图片 URL 是否与当前域名一致,CDN 域名不同即算跨域
- 服务端需为图片响应头添加
Access-Control-Allow-Origin: *(仅对支持 CORS 的平台有效,部分小程序仍受限) - 更可靠方案:用
uni.downloadFile先下载到本地临时路径,再用uni.getImageInfo读取本地文件,绕过跨域限制 - 注意:iOS 小程序对本地临时路径的读取有时效性,需在 downloadFile success 后立即处理


















