导读:本期聚焦于苏锦程创作的《Vue 3 中如何实现水印功能?Canvas 绘制与防篡改方案详解》,敬请观看详情。水印是企业后台系统里常见的需求,通常用来防止信息截图外泄并追溯到具体人员。本文围绕 Vue 3 展开,介绍如何用 Canvas 动态生成文字水印并平铺到页面上,包括画布创建、文字旋转、透明度控制和 canvas.toDataURL 转图片的核心原理。针对用户通过开发者工具删除水印节点的绕过手段,进一步给出基于 MutationObserver 的防篡改方案,监听节点增删和样式修改并自动恢复水印。文末还封装了一个可复用的 useWatermark 组合式函数,支持自定义文字、颜色、字号与 z-index,可直接集成到任意 Vue 3 项目中。

企业内部系统、数据大屏、订单详情页,这类页面经常需要叠加一层水印,一方面起到警示作用,另一方面一旦发生截图外泄,可以通过水印内容快速定位到泄露源头。在 Vue 3 中实现水印并不复杂,核心思路是用 Canvas 画出水印图案,转成图片后以背景图的方式平铺在页面上。但真正麻烦的地方在于防篡改——只要用户打开开发者工具,删除水印所在的 DOM 节点,水印就没了。本文就从绘制和防护两个层面,完整讲清楚水印功能的实现。

Vue 3 中如何实现水印功能?Canvas 绘制与防篡改方案详解

一、用 Canvas 动态生成水印图片

水印的本质是一张重复平铺的小图片。我们可以用 Canvas 创建一个画布,在画布上绘制旋转后的文字,再通过 canvas.toDataURL() 导出为 base64 图片。相比直接让后端返回一张固定水印图,前端动态生成的好处是可以实时携带当前登录用户的姓名、工号,不需要为每个人准备一份图片资源。

下面是核心的绘制函数。创建一个 200 x 160 的画布,先把坐标系平移到中心点,再旋转负 20 度,这样文字会呈现倾斜的效果。设置合适的透明度和字体后,用 fillText 写入水印文字:

function createWatermark(text) {
  const canvas = document.createElement('canvas');
  canvas.width = 200;
  canvas.height = 160;
  const ctx = canvas.getContext('2d');

  ctx.translate(100, 80);   // 坐标原点移动到画布中心
  ctx.rotate(-20 * Math.PI / 180); // 旋转 -20 度
  ctx.font = '16px "Microsoft YaHei"';
  ctx.fillStyle = 'rgba(0, 0, 0, 0.08)'; // 半透明,不遮挡正文内容
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(text, 0, 0);

  return canvas.toDataURL('image/png');
}

拿到 base64 图片后,创建一个绝对定位的 <div>,设置 position: fixed 让它覆盖整个视口,再把图片设置为 background-image 并用 repeat 平铺,pointer-events: none 保证水印层不拦截任何鼠标事件,用户依然可以正常点击页面上的按钮和链接。

二、封装成可复用的组合式函数

在 Vue 3 里更优雅的做法是把水印逻辑抽成 useWatermark 组合式函数,组件挂载时创建水印,卸载时清理节点和监听器,同时对外暴露修改水印内容的接口。这样做的好处是任何页面调用一行代码就能启用水印,逻辑完全收敛在一个文件里,后续要改样式只需要改一处。

实现时需要注意两点:第一,水印节点应该挂到 document.body 下而不是组件内部,否则组件卸载时水印会跟着消失;第二,要记录上一次的节点引用,避免重复调用时产生多个水印层叠加,导致透明度变深。示例代码如下:

import { onMounted, onBeforeUnmount, ref } from 'vue';

export function useWatermark() {
  let watermarkEl = null;
  const watermarkText = ref('');

  function setWatermark(text) {
    watermarkText.value = text;
    removeWatermark();
    watermarkEl = document.createElement('div');
    watermarkEl.style.cssText = `
      position: fixed;
      inset: 0;
      z-index: 9999;
      pointer-events: none;
      background-image: url(${createWatermark(text)});
      background-repeat: repeat;
    `;
    watermarkEl.id = 'app-watermark';
    document.body.appendChild(watermarkEl);
  }

  function removeWatermark() {
    if (watermarkEl && watermarkEl.parentNode) {
      watermarkEl.parentNode.removeChild(watermarkEl);
    }
    watermarkEl = null;
  }

  onBeforeUnmount(removeWatermark);

  return { setWatermark, removeWatermark, watermarkText };
}

在组件中使用非常简单,调用 setWatermark('张三 - 10086') 即可看到效果。如果页面切换了用户,再次调用这个函数会先移除旧水印再创建新的,保证页面上始终只有一层水印。

三、防篡改:用 MutationObserver 监听并自动恢复

到目前为止,水印功能已经可用,但存在一个致命漏洞:用户按 F12 打开开发者工具,选中水印节点按 Delete 删除,或者把 display 改成 none,水印就失效了。对于安全要求较高的场景,必须加上防篡改机制。

浏览器提供了 MutationObserver API,它可以监听 DOM 树的变化。我们把监听目标设为 document.body,开启 childList(子节点增删)、attributes(属性修改)、subtree(后代节点)三个选项。一旦回调被触发,就检查水印节点是否还存在、样式是否被改,如果发现异常立即重新生成:

let observer = null;

function observeWatermark() {
  observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      // 水印节点被删除,或其属性(style、class)被修改
      const removed = Array.from(mutation.removedNodes);
      const target = watermarkEl;
      if (!target) return;

      const isRemoved = removed.includes(target);
      const isAttrChanged =
        mutation.type === 'attributes' && mutation.target === target;

      if (isRemoved || isAttrChanged) {
        observer.disconnect(); // 先断开监听,避免恢复操作再次触发死循环
        setWatermark(watermarkText.value); // 重新绘制并挂载水印
        break;
      }
    }
  });

  observer.observe(document.body, {
    childList: true,
    attributes: true,
    subtree: true,
  });
}

这里有一个非常关键的细节:恢复水印的操作本身也会触发 MutationObserver 的回调,如果不做处理就会陷入无限循环。解决办法是在恢复前先调用 observer.disconnect() 断开监听,恢复完成后再重新 observe,这样逻辑就不会自触发。另外,由于开启了 subtree,页面上任何正常的 DOM 变化都会进入回调,回调内部应尽早做判断和返回,避免影响页面性能。

还有一点值得说明:纯前端的防篡改只能提高破解成本,无法做到绝对安全。用户可以禁用 JavaScript、用截图工具遮挡、或者通过代理拦截接口数据。因此水印方案应与后端水印(服务端在返回的图片上打水印)、敏感数据脱敏等措施配合使用,形成多层防护。前端 MutationObserver 的价值在于挡住随手删除 DOM 这种最常见的绕过方式,对绝大多数普通用户已经足够。

四、功能扩展与常见问题

实际项目中还可以对这个基础方案做不少扩展。比如支持多行水印,在 fillText 之前先按行拆分文字,逐行绘制并调整 y 坐标;比如需要更强的防截图能力时,可以把水印透明度调高、密度调大,在安全性和阅读体验之间权衡;再比如某些场景只需要局部水印,把 position: fixed 改成 absolute 并挂载到指定容器内即可,此时 MutationObserver 的监听目标也要相应改成该容器的父节点。

常见问题方面,一是部分浏览器对 base64 背景图有长度限制,水印文字过长时建议拆成多行而不是拉宽画布;二是 z-index 要设置得足够大,避免被弹窗组件遮挡,但也不能大到盖住系统级提示;三是移动端高 DPI 屏幕上水印可能发虚,可以按 window.devicePixelRatio 放大画布尺寸再缩放绘制,让文字保持清晰。把这些细节处理到位,一个生产可用的水印功能就完整落地了。

Vue3水印Canvas绘制防篡改修改时间:2026-09-03 16:15:10

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49670.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。