uni-app中canvas绘图必须用uni.createCanvasContext创建上下文,不可操作DOM;需在onReady后调用,绘制完成后再通过ctx.draw(true, callback)触发canvasToTempFilePath导出,注意多端像素比、图片预加载、权限申请及格式兼容。

uni-app 里 canvas 绘图必须用 uni.createCanvasContext,不能直接操作 DOM
uni-app 的 canvas 不是浏览器原生 <canvas>,而是通过 native 层桥接的渲染节点。你写 document.getElementById 或 querySelector 拿不到上下文,也调用不了 getContext('2d') —— 这个方法在 H5 端可能“看起来”能用,但小程序和 App 端直接报错或白屏。
正确做法是:先用 <canvas canvas-id="myCanvas" /> 声明,再通过 uni.createCanvasContext('myCanvas', this) 创建上下文(注意第二个参数必须传当前 Vue 实例)。
- canvas-id 必须唯一且不含特殊字符,否则 iOS 小程序会静默失败
- H5 端若想兼容调试,可加一层判断:
if (uni.getSystemInfoSync().platform === 'h5') { /* 降级用原生 canvas */ },但不推荐长期混用 - 绘图前务必等
onReady触发后再创建 context,否则某些平台(如微信小程序真机)context 为空
生成海报后保存到相册,uni.canvasToTempFilePath 要传对参数
这个 API 是关键转折点,不是所有字段都可省略。尤其容易漏掉 canvasId 和 success 回调里的 tempFilePath 处理逻辑 —— 它返回的是临时路径,不能直接传给 uni.saveImageToPhotosAlbum,得先用 uni.getFileSystemManager().readFile 或直接走 success 回调链。
- 必须指定
canvasId,且值要和模板中一致;漏写或拼错会导致 “canvas not found” 错误 - width/height 建议显式传入(单位 px),不要依赖 canvas 元素宽高,否则安卓部分机型截图模糊或裁切
- 真机调试时,
tempFilePath在 success 回调里才有,别在回调外 console.log 它——此时还是 undefined - 微信小程序需提前在
manifest.json里声明scope.writePhotosAlbum权限,否则saveImageToPhotosAlbum直接 fail
多端兼容 canvas 渲染细节:字体、图片、像素比要手动适配
不同平台 canvas 的默认像素比(devicePixelRatio)不一致:iOS 微信常为 3,Android 可能是 2 或 1,H5 则取决于浏览器缩放。直接写 ctx.setFontSize(14) 在高清屏上文字会极细甚至看不见;用 ctx.drawImage(img, x, y) 加载网络图,没等图片加载完就 draw,结果空白。
- 统一按 2 倍屏处理:获取
uni.getSystemInfoSync().pixelRatio,然后把画布宽高设为设计稿尺寸 × pixelRatio,并在canvasToTempFilePath中传对应 width/height - 字体大小建议 ≥16px,或用
ctx.setFontSize(16 * pixelRatio)动态调整 - 图片资源必须用
uni.getImageInfo预加载,拿到path后再传给ctx.drawImage,否则小程序端无法绘制网络图 - 避免使用
ctx.measureText计算文本宽度——它在各端返回值差异大,改用固定留白或分段截断更稳
保存失败常见原因:权限、路径、格式三连坑
用户点击“保存海报”后无反应或弹出“保存失败”,大概率卡在这三个地方:权限没申请、临时文件被 GC 回收、输出格式不支持。
- 安卓手机首次调用
uni.saveImageToPhotosAlbum前,必须先uni.authorize({ scope: 'scope.writePhotosAlbum' }),且要 catch 授权拒绝逻辑,不能只靠 success 走流程 -
canvasToTempFilePath返回的tempFilePath是临时路径,有效期约 24 小时,但若页面跳转或组件销毁,部分安卓机型可能提前释放——务必在同一个 tick 内连续调用 save - 微信小程序要求保存的图片是 PNG 或 JPG 格式,如果 canvas 内容含透明通道又导出为 JPG,背景会变黑;建议统一导出 PNG,
fileType: 'png'显式声明
最易被忽略的一点:canvas 绘制过程是异步的,但 canvasToTempFilePath 不会等你 draw 结束——必须用 ctx.draw(true, () => { /* 这里再调用 canvasToTempFilePath */ }) 确保绘制完成。漏掉这个回调,海报永远是空白。


















