uni-app水印无法全屏的主因是fixed定位在iOS微信等WebView中被截断或降级为absolute,且根节点外DOM易受生命周期干扰;应改用onShow中动态创建canvas并设fixed全屏样式,配合resize重绘实现真全屏覆盖。

uni-app 里水印覆盖无法全屏的常见原因
直接用 position: fixed 或 z-index 拉高,在 iOS 微信、支付宝等 WebView 中大概率失效——因为这些容器会截断或忽略超出视口的层叠上下文,且部分平台对 fixed 定位做降级处理(转为 absolute)。更关键的是:uni-app 的 vue 编译模式下,App.vue 根节点外的 DOM 不受 Vue 响应式控制,手动插入的水印 div 容易被生命周期销毁或样式隔离干扰。
用 onShow + 动态插入 canvas 实现真全屏水印
不依赖 CSS 层叠,改用 Canvas 绘制后作为背景图贴在最底层。Canvas 可随屏幕 resize 自动重绘,且微信/支付宝/APP 端均支持 canvasToTempFilePath(但此处不用导出,只用作渲染层)。
实操要点:
- 在页面
onShow钩子中获取uni.getSystemInfoSync()的screenWidth和screenHeight - 用
uni.createCanvasContext('watermark-canvas', this)创建上下文,注意 canvas id 必须全局唯一且不能含变量 - 文字水印建议用
fillText分块绘制(避免单行过长导致 iOS 截断),字号设为14px左右,透明度0.08–0.12(太淡无效,太浓影响阅读) - 绘制完成后调用
context.draw(false, () => {...}),第二个参数是回调,确保绘制完成再显示 - 把 canvas 放在页面最外层
view内,并设style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none;"
防截屏的关键:水印必须「不可剥离」
单纯盖一层 canvas 或 image,截图后仍可被裁剪、PS 去除。真正有效的方案是让水印和业务内容融合——比如在每个敏感 text、image 组件上叠加半透水印浮层,或使用 background-image 的 repeating-linear-gradient + 文字 base64 图案(需预生成)。
更稳妥的做法:
- 对核心数据字段(如手机号、金额)用
v-for拆成单字符,每个字符外层包一个view,再通过::after伪元素加旋转水印(仅 H5 端有效) - App 端启用
plus.screen.lockOrientation('portrait-primary')并监听plus.screen.onorientationchange,每次切屏重绘 canvas 水印(防止横竖屏错位) - 微信小程序需额外配置
"render": "native"(在mp-weixin下),否则 canvas 在某些机型上模糊或不触发 draw
按钮触发水印的性能与兼容陷阱
点击按钮才开启水印,看似合理,但容易掉进两个坑:一是按钮事件未绑定到根 page 层,导致 APP 端点击无响应;二是水印初始化写在 methods 里却没做防抖,快速连点可能创建多个 canvas 实例。
正确做法:
- 按钮用
<button @click="toggleWatermark" hover-class="none">,hover-class="none"避免 iOS 点击灰块遮挡水印 -
toggleWatermark内先检查this.watermarkActive状态,再调用绘制逻辑;关闭时用uni.removeCanvas(仅 App 端)或直接uni.hideToast()类似方式隐藏(H5 就 display:none) - Android 真机测试时,若发现水印闪烁,大概率是
canvas被 uni-app 的 diff 渲染机制重复挂载,解决方案:把 canvas 放在onReady后动态document.body.appendChild,绕过 Vue 模板编译
水印不是加个 div 就完事,它得活在系统渲染管线的最底层,且每次屏幕变化都要重新锚定——这点在折叠屏、分屏或多窗口场景下特别容易漏掉。


















