Vue3水印应作用于真实DOM而非虚拟DOM,通过onMounted+canvas绘制+MutationObserver防篡改实现;推荐自定义指令方式,轻量可控,支持动态配置与全局注入。

不能手动拦截虚拟 DOM 的创建过程。Vue 或 React 的虚拟 DOM 是框架内部机制,不对外暴露“创建前钩子”或可拦截的中间层。所谓“拦截创建”,本质上是对组件渲染逻辑施加控制,而非篡改 vdom 构造本身。
水印应作用于真实 DOM,而非虚拟 DOM
水印是视觉层覆盖物,必须挂载到真实节点上才能生效。虚拟 DOM 只是 JS 对象,无法直接绘制、定位或响应鼠标事件。因此,正确路径是:在组件挂载后(mounted),遍历目标容器的真实 DOM 节点,动态插入水印 canvas 或伪元素。
- Vue 中用 onMounted + nextTick 确保 DOM 已渲染
- React 中用 useEffect(() => { ... }, []) 执行一次注入
- 避免操作 vdom 对象(如 render 函数返回值),它不可写、无生命周期、不触发重绘
实现一个可复用的全局水印指令(Vue 3)
比全局组件更轻量、更可控的方式是自定义指令,自动为任意容器添加水印:
const watermark = {
mounted(el, binding) {
const options = {
text: binding.value?.text || '内部资料',
fontSize: binding.value?.fontSize || '16px',
rotate: binding.value?.rotate || -25,
opacity: binding.value?.opacity || 0.08,
gapX: binding.value?.gapX || 100,
gapY: binding.value?.gapY || 100,
...binding.value
};
<pre class="brush:php;toolbar:false;">const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 200;
ctx.rotate((options.rotate * Math.PI) / 180);
ctx.font = `bold ${options.fontSize} Microsoft Yahei`;
ctx.fillStyle = 'rgba(0, 0, 0, ${options.opacity})';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(options.text, 0, 0);
const url = canvas.toDataURL('image/png');
el.style.position = 'relative';
el.style.overflow = 'hidden';
const watermarkEl = document.createElement('div');
watermarkEl.style.cssText = `
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
background-image: url(${url});
background-repeat: repeat;
background-size: ${options.gapX}px ${options.gapY}px;
z-index: 1;
opacity: ${options.opacity};
`;
el.appendChild(watermarkEl);}, unmounted(el) { const wm = el.querySelector('[style*="watermark"]'); if (wm) wm.remove(); } };
// 全局注册 app.directive('watermark', watermark);
使用方式:
若坚持用全局组件,需主动包裹 + 透传内容
全局水印组件不是“自动注入”,而是作为壳组件显式包裹目标区域:
<watermark text="测试水印"><div class="content">这里会被打上水印</div> </watermark>
其内部实现仍依赖 slot + mounted,在 onMounted 后查找 .content 或直接操作 $slots.default()[0].el(Vue 3 setup 中需配合 ref 获取根元素),再注入 canvas 层。它无法“无感全局生效”,必须参与模板结构。
绕过框架的底层方案(仅限高级场景)
极少数需求(如全站无侵入水印)可监听 DOM 变化,用 MutationObserver 捕获新挂载的容器,再统一加水印:
- 监听 document.body,过滤出具有特定 class(如 watermark-target)的新增节点
- 对每个匹配节点调用水印注入函数(同上 canvas 方案)
- 注意避免重复注入、内存泄漏、SSR 不兼容等问题
这种方式脱离框架生命周期,适合中后台系统统一管控,但调试成本高、与 Vue/React 更新机制脱节,不推荐作为首选。










