必须用+drawimage/filltext前端重绘,uni-app的canvas不支持像素级操作,仅认uni.canvastotempfilepath导出;canvas-id须硬编码且唯一;图片需转base64或真实路径;文字绘制需设baseline、用px单位;导出前须clearrect、draw回调、显式宽高。

直接上结论:必须用 <canvas></canvas> + drawImage / fillText 前端重绘,不能靠 CSS 叠加、不能依赖服务端、也不能用原生 toDataURL——uni-app 的 canvas 是封装层,不支持像素级操作,只认 uni.canvasToTempFilePath 导出。
为什么 canvas.id 必须写死且不能动态绑定
很多同学把 canvas-id 写成 :canvas-id="id" 或拼接字符串,结果 uni.createCanvasContext 拿不到上下文,静默失败。
-
canvas-id是字符串字面量,必须在 WXML 中硬编码,例如canvas-id="watermarkCanvas" - 动态绑定(如
:canvas-id="`canvas-${index}`")在小程序和 App 端完全无效,H5 可能侥幸跑通但多端不一致 - 同一页面多个 canvas 时,每个
canvas-id必须唯一,且调用uni.createCanvasContext('xxx', this)时第二个参数this不能省略(否则 nvue 下失效)
图片加载失败的三个高频原因
ctx.drawImage 不报错、不提示、不画图——是静默失败,不是逻辑卡住。排查优先看这三点:
- 路径是相对路径(如
./static/logo.png)→ 小程序不识别,必须转为 base64 或用uni.getRealPathSync转真实路径 - 路径是网络地址(如
https://xxx.com/logo.png)→ 小程序 canvas 明确禁止跨域图片,会白屏;App/H5 也可能因 CORS 拒绝加载 - 图片尺寸过大或格式异常(比如带 alpha 通道的 WebP 在部分安卓机解码失败)→
img.onload根本不触发,后续绘制全跳过
稳妥做法:把 Logo 放进 static/ 目录,构建时自动内联为 base64;或用 uni.getFileSystemManager().readFile 同步读取本地文件再创建 canvas.createImage()。
文字水印位置偏移、模糊、截断的根因
你以为 ctx.fillText('©2026', 20, 40) 就完事了?iOS 微信小程序里中文可能左偏 10px,安卓低端机上文字糊成一团,甚至整行消失。
-
fillText的y是基线(baseline),不是顶部,不设setTextBaseline('middle')就容易“画飞” - 字体大小必须用
px(如ctx.setFontSize(18)),禁用rpx或%,iOS 小程序里 rpx 字体会缩成一条线 - 旋转水印必须先
ctx.translate(x, y)移动原点,再rotate,最后fillText画在(0, 0),否则旋转中心错位 - 透明度别用
ctx.globalAlpha = 0.3,它会影响后续所有绘制;改用ctx.setFillStyle('rgba(0,0,0,0.3)')
导出图片为空白或尺寸不对
uni.canvasToTempFilePath 返回的临时路径打开是空白图?或者宽高和 canvas 设置严重不符?大概率是这几个动作没做对:
- canvas 元素的
width和height属性(不是style.width)必须显式设置为数字,例如width="800" height="600";仅靠 CSS 缩放会导致导出分辨率错乱 - 绘制前必须调用
ctx.clearRect(0, 0, width, height),否则旧帧残留,尤其多图连续处理时极易复用上一张内容 - 调用
canvasToTempFilePath前,必须确保ctx.draw(false, () => {...})的回调已执行——很多人漏掉这个回调,导致 canvas 还没真正绘制完就导出了 - 真机调试时,Android 部分机型(如 MIUI)要求 canvas 宽高必须是偶数,奇数值可能导致导出失败或黑边
最易被忽略的一点:iOS App 端用 uni.chooseImage 拿到的 tempFilePath 是沙盒路径,不能直接传给 drawImage;必须先 uni.getFileSystemManager().readFile 读成 base64,再构造 data:image/png;base64,xxx 使用。











