uni-app图片加水印必须用canvas前端绘制,因原生不支持且多端行为差异大;需显式设置canvas宽高、用px字体、clearRect清空、分平台加载图片,并在draw回调中导出。

uni-app 中图片选择后加水印的核心限制
uni-app 本身不提供原生图片编辑能力,uni.chooseImage 只返回临时路径或 base64,后续必须用 canvas 手动绘制水印。这意味着:水印不是“附加元数据”,而是真正重绘一张新图;真机调试时 canvas 在 iOS 和 Android 的像素对齐、字体渲染、drawImage 缩放行为存在差异,直接套 Web 写法大概率出错。
关键判断:不能依赖服务端加水印(用户选完图就需立即预览),也不能用第三方 SDK(多数不兼容 uni-app 多端编译),唯一可行路径是「前端 canvas 绘制 + 适配多端 canvas 行为」。
用 uni.createCanvasContext 绘制文字水印的实操要点
常见错误是直接在 H5 上调通,一到小程序或 App 就水印偏移、模糊、不显示。根本原因是:uni.createCanvasContext 返回的上下文默认使用 rpx 坐标系,但 drawImage 的宽高参数却是 px 值,且不同平台对 canvas.width/canvas.height 的设置响应不一致。
- 务必显式设置 canvas 元素的
width 和 height 属性(非 style),值建议取目标图宽高的 1:1,例如原图 800×600,canvas 就设 width="800" height="600"
- 文字水印优先用
ctx.fillText 而非 ctx.strokeText,后者在部分安卓机型上会渲染异常
- 字体大小统一用 px 单位(如
ctx.setFontSize(24)),禁用 rpx 或 %,否则 iOS 微信小程序里字体可能缩成一条线
- 水印旋转角度建议 ≤ 25°,超过后某些低端安卓机 canvas 会截断文字
- 绘制前必须调用
ctx.clearRect(0, 0, width, height),否则旧内容残留
将 base64 图片加载进 canvas 的兼容写法
uni.chooseImage 返回的 tempFilePath 在 H5 是 blob URL,在小程序是本地文件路径,在 App(尤其是 iOS)可能需要先转成 base64 才能被 canvas 正确加载。
width 和 height 属性(非 style),值建议取目标图宽高的 1:1,例如原图 800×600,canvas 就设 width="800" height="600"
ctx.fillText 而非 ctx.strokeText,后者在部分安卓机型上会渲染异常ctx.setFontSize(24)),禁用 rpx 或 %,否则 iOS 微信小程序里字体可能缩成一条线ctx.clearRect(0, 0, width, height),否则旧内容残留uni.chooseImage 返回的 tempFilePath 在 H5 是 blob URL,在小程序是本地文件路径,在 App(尤其是 iOS)可能需要先转成 base64 才能被 canvas 正确加载。
正确流程:
uni.chooseImage({
success: (res) => {
const tempPath = res.tempFiles[0].path;
// H5 和 App 需转 base64,小程序可直读
if (uni.getSystemInfoSync().platform === 'h5' ||
uni.getSystemInfoSync().platform === 'ios') {
uni.getFileSystemManager().readFile({
filePath: tempPath,
encoding: 'base64',
success: (readRes) => {
const imgSrc = 'data:image/png;base64,' + readRes.data;
drawWatermark(imgSrc); // 后续绘制函数
}
});
} else {
drawWatermark(tempPath);
}
}
});
注意:uni.getFileSystemManager().readFile 在微信小程序中不可用,所以必须按平台分支处理;App 端若用 plus.io 读取文件,路径格式与小程序完全不同,容易报 fail no such file 错误。
生成带水印的图片并保存的坑点
canvas.toTempFilePath 是最终导出的关键,但它在各端行为割裂严重:
- 微信小程序:必须指定
canvasId,且该 canvas 必须已挂载到 DOM(v-if 切换中未渲染会导致 fail canvas is not found)
- H5:
toTempFilePath 不可用,必须用 canvas.toDataURL('image/png'),再用 uni.downloadFile 模拟保存(实际是触发浏览器下载)
- App:iOS 需确保 canvas 已渲染完成(加
setTimeout 100ms 延迟调用),Android 则对 quality 参数敏感,设 0.8 以上才清晰
- 所有平台都禁止在未完成绘制时调用导出,必须等
ctx.draw(true, () => { ... }) 回调中执行
canvasId,且该 canvas 必须已挂载到 DOM(v-if 切换中未渲染会导致 fail canvas is not found)toTempFilePath 不可用,必须用 canvas.toDataURL('image/png'),再用 uni.downloadFile 模拟保存(实际是触发浏览器下载)setTimeout 100ms 延迟调用),Android 则对 quality 参数敏感,设 0.8 以上才清晰ctx.draw(true, () => { ... }) 回调中执行最易忽略的一点:水印文字如果用了自定义字体(如从 static/font.ttf 加载),H5 可用 @font-face,但小程序和 App 完全不支持——只能用系统默认字体(sans-serif)或提前转成图片水印。


















