小程序水印需用全局注册的WaterMark组件实现,避免fixed失效和DOM插入限制;Canvas方案须注意绘制时机、尺寸适配及坐标系差异;敏感水印内容必须由服务端签名透传,前端不可拼接。

小程序端水印不能靠 App.vue 里写 fixed div
直接在 App.vue 的 <template> 里加一个 position: fixed 水印层,看起来能覆盖全屏,但真机(尤其是 iOS 微信)会降级 fixed 为 absolute,导致水印只盖住内容区,状态栏、顶部导航栏、底部 tabbar 全部漏出。更麻烦的是:小程序不支持全局 DOM 插入,document.body.appendChild 类操作在真机上无效或被拦截。
必须用组件 + 全局注册 + v-if 控制显隐
推荐路径是封装 WaterMark 组件,在 main.js 中全局注册,再由业务逻辑控制开关:
- 组件用
<view class="watermark-wrapper">包裹,样式中必须设position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; pointer-events: none; - 文字用
v-for多行重复铺满,避免单行被裁剪;字体单位用rpx或vw,别用px - 旋转角度加
-webkit-transform: rotate(-12deg)(iOS 小程序必须加,否则锯齿严重) - 透明度设为
opacity: 0.08左右,太浅看不见,太深干扰阅读 - 注册后,在需要水印的页面模板里写
<watermark v-if="showWatermark"></watermark>,showWatermark由 pinia store 或路由守卫控制
Canvas 方案在小程序里极易错位
用 uni.createCanvasContext('watermark-canvas', this) 绘制水印,常见失效原因不是 API 调用错,而是时机和尺寸没对上:
- 别在
onLoad里立刻 draw —— 此时视图还没 layout 完,canvas宽高可能是 0;改用$nextTick(() => { ... })或监听uni.onWindowResize - canvas ID 必须唯一,且模板中要提前声明:
<canvas canvas-id="watermark-canvas" class="watermark-canvas"></canvas> - H5 和小程序的 canvas 坐标系不同:小程序 y 轴正向向下,文字 baseline 默认是
middle,需手动调ctx.setTextAlign('center')和ctx.setTextBaseline('middle') - 横竖屏切换、键盘弹起都会触发重排,不监听
uni.onWindowResize就必然错位
服务端签名文本必须透传,不能前端拼接
水印内容若含用户姓名、时间、设备 ID 等敏感信息,绝不能在 JS 里拼:this.userInfo.name + '_' + new Date().toISOString().slice(0,10) —— 这类字符串可被调试器实时修改,毫无防伪意义。
正确做法是:
- 登录成功后,服务端返回带签名的水印结构,例如:
{"text":"张三_20260617_7d8a2f","sign":"sha256-xxx"} - 前端只取
text字段传给水印组件或 Canvas context.fillText() - 若使用第三方插件(如
watermark-plus),确认其原生侧是否校验sign;否则签名只是摆设
小程序端没有原生窗口绘制能力,所以 Canvas + 组件方案已是上限;真正要防截图/录屏,得走 App 端原生插件,小程序本身不具备该权限。


















