uni-app 的 canvas API 与原生 Canvas 不同,不支持 getImageData、toDataURL 等方法,水印需用 drawImage 和 fillText 分层绘制,并通过 canvasToTempFilePath 导出;须注意路径限制、id 声明、坐标系、旋转、字体渲染、尺寸适配及多端兼容性问题。

uni-app 的 canvas API 和原生 Canvas 不一样
uni-app 的 canvas 是通过 uni.createCanvasContext 创建的,它不返回标准 DOM Canvas 对象,也不支持直接操作 ImageData 或使用 toDataURL 等原生方法。这意味着你不能像在 H5 里那样用 ctx.getImageData 读像素、加水印再写回——这条路在小程序端根本走不通。
实操建议:
- 水印必须通过
drawImage(贴图) +fillText(文字)分层绘制,最后用canvasToTempFilePath导出 - 所有图片资源(包括水印图)必须是本地路径或临时路径,网络地址需先
uni.downloadFile下载到本地 - 注意 canvas 的
id必须在 WXML 中显式声明,且不能动态绑定(比如:id="canvasId"会失效) - 真机调试时,iOS 微信小程序对
fillText字体渲染有 bug,中文可能偏移或截断,建议用固定字号 +textAlign: 'center'+ 手动计算 x 坐标
文字水印:fillText 的坐标和旋转陷阱
很多人以为 ctx.fillText('©2024', x, y) 一写就完,结果水印歪斜、重叠或跑画布外。核心问题是:canvas 坐标系原点在左上角,y 是文字基线位置,不是顶部;旋转后坐标变换容易失控。
实操建议:
- 用
ctx.setTextAlign('center')和ctx.setTextBaseline('middle')统一对齐方式,避免靠猜 - 旋转水印时,先
ctx.translate(x, y)移动原点,再ctx.rotate(angle * Math.PI / 180),最后ctx.fillText画在 (0, 0) —— 否则旋转中心不对 - 字号建议设为
14~20,太小看不清,太大在低端安卓机上易模糊 - 透明度用
ctx.setFillStyle('rgba(0, 0, 0, 0.2)'),别用globalAlpha,后者会影响后续所有绘制
图片水印:drawImage 的尺寸与裁剪风险
直接 ctx.drawImage(watermarkUrl, x, y, width, height) 很容易导致水印拉伸变形、位置错乱,尤其当水印图本身分辨率高但 canvas 尺寸小的时候。
实操建议:
- 务必提前用
uni.getImageInfo获取水印图真实宽高,按比例缩放,保持宽高比(例如scale = Math.min(60 / w, 60 / h),控制水印最大边为 60rpx) - 不要依赖 WXML 中
canvas组件的宽高样式,而是用query.select('.my-canvas').boundingClientRect()拿实际渲染尺寸 - 如果要铺满背景式水印,用循环绘制,但注意性能:单张图重复画 20+ 次可能卡顿,建议生成一张带水印的底图再贴一次
- 小程序端
drawImage不支持 SVG,水印图必须是 PNG 或 JPG
canvasToTempFilePath 失败的常见原因
调用后没报错但拿不到文件,或者返回 “fail canvas is empty”,基本是时机或上下文问题。
实操建议:
- 必须等
ctx.draw(true, () => { ... })的回调执行完,再调canvasToTempFilePath;异步绘制未完成就导出,必然为空 - 微信小程序要求 canvas 必须在页面可视区域(哪怕
opacity: 0),WXML 中不能加v-if或display: none - H5 端不支持
canvasToTempFilePath,需降级用uni.canvasToTempFilePath并 catch 错误,改用toDataURL回退 - 导出前检查
ctx是否已销毁(如页面跳转后还在 draw),加个if (!this.ctx) return防 crash
最麻烦的其实是多端一致性:H5 能直接操作像素,小程序只能绘图导出,App 端还可能因原生 canvas 实现差异出现字体偏移。做之前先明确目标平台,别指望一套代码全端通用。

















