页面级水印不能仅靠CSS fixed全局覆盖,因iOS微信/支付宝WebView会降级fixed为absolute导致覆盖不全,且uni-app根外DOM不受生命周期保护;应使用全局WaterMark组件+动态props+服务端签名文本,并按环境分路径:H5/小程序用Canvas监听resize重绘,App端必须交由原生层实现。
页面级水印不能靠 CSS fixed 全局覆盖
直接在 app.vue 或某个页面里写 position: fixed + z-index 拉高,看似简单,但在 ios 微信、支付宝 webview 里大概率失效——这些容器会截断或降级 fixed 为 absolute,导致水印只盖住内容区,状态栏、导航栏、键盘弹出后区域全空白。更关键的是:uni-app 的 vue 编译机制下,根节点外手动插入的 dom 不受生命周期保护,tab 切换、页面卸载时容易残留或丢失。
推荐用全局组件 + v-if 控制显隐
创建 components/WaterMark/index.vue,用 position: fixed 布局,但不硬编码内容,而是通过 props 或 inject 接收动态文本:
- 在
main.js中注册为全局组件:Vue.component('WaterMark', WaterMark) - 所有需要水印的页面模板中统一加一行:
<watermark v-if="showWatermark"></watermark> -
showWatermark可由路由守卫、onShow钩子或 pinia/store 控制,比如登录后才设为true - 避免在
App.vue里直接挂载——它不参与页面级生命周期,onShow触发时水印可能还没渲染完
水印文字必须服务端签名,前端只透传
别在 JS 里拼接 this.userInfo.name + new Date().toLocaleDateString()。这种写法毫无防伪意义:用户禁用 JS、抓包改响应、甚至直接调试器改变量都能绕过。真实业务中,水印文本应由服务端生成并附带校验签名,例如返回:
{"text": "张三_20260529_8a3f7e1d", "sign": "sha256..."}
前端拿到后,只把 text 字段传给水印组件或原生插件;若使用 watermark-plus 这类插件,需确认它支持服务端校验逻辑(否则签名形同虚设)。
Android 水印偏移、iOS 截图失效是常态
Canvas 绘制方案在 H5 和小程序端表现尚可,但 App 端仍要面对原生层限制:
- Android 全面屏/刘海屏下,
screenWidth和screenHeight返回的是虚拟尺寸,得调用plus.screen.getRealSize()获取真实像素 - iOS 截图时,WebView 层水印必然被剥离——真防截屏必须走原生插件,在
UIWindow层绘制,且要监听plus.screen.onorientationchange重绘 - 横竖屏切换、软键盘弹出都会触发视图重排,Canvas 水印若没监听
resize或uni.onWindowResize,立刻错位
真正稳定的页面级水印,从来不是“画一层就完事”,而是根据运行环境选路径:H5/小程序用 Canvas 动态渲染,App 端必须交还给原生层,且服务端签名和客户端校验缺一不可。











