别再写易破解的Canvas水印了!MutationObserver防篡改水印,从原理到完整代码(直接复制)

最近公司的系统要升级,原来的水印部分也是我做的,用的是最简单的Canvas实现的。

效果其实不错,之前用着也一直没啥问题。

但是客户那边最近好像有个懂技术的人加入,说这种水印不太合规。

正好借着系统设计让我们把这里给改了。

为什么普通水印一删就没?

之前做水印,其实无外乎两个方案,要么是背景图(CSS),要么是 Canvas/SVG 生成的元素(JS)。

  • CSS 水印:直接在 F12 中找到对应样式,删除 background-image 就没了。
  • 普通 Canvas/SVG 水印:找到对应的 DOM 节点,右键删除,水印直接消失。

就是说在前端其实可以隐藏掉相应的水印。

MutationObserver 防篡改水印

MutationObserver 是浏览器原生提供的 DOM 变化监听 API,用于监听 DOM 节点的增删、属性修改、子树变化,触发变化后执行自定义回调。

核心就是监控这个水印节点,只要它被删、被改,就立刻自动重建。

形成"删一次、建一次"的死循环,这也是它删不掉的关键。

这个 API 是浏览器原生支持的,不需要引入任何第三方插件,兼容性覆盖所有现代浏览器(IE 除外,现在新系统的开发基本不考虑 IE 了)。

完整代码

这里基于 SVG 实现,比 Canvas 更简洁、无跨域问题。

包含水印生成、挂载、监听全流程,兄弟们可以直接集成到项目中。

最终效果

  • 全屏斜纹水印,不影响页面点击、输入;

  • F12 删除水印节点 → 自动恢复;

  • 修改水印样式(display: none、opacity: 0)→ 自动恢复;

  • 修改水印层级(z-index)→ 自动恢复。

js 复制代码
// 生成水印(SVG 格式,简洁无跨域)
function createWatermark() {
    const watermarkText = "这是水印信息";
    const fontSize = 16; // 水印字体大小
    const opacity = 0.1; // 水印透明度(0-1,越小越淡)
    const angle = -20; // 水印倾斜角度(负号为向左倾斜)
    const gapX = 220; // 水印横向间距
    const gapY = 200; // 水印纵向间距

    // SVG 水印模板(无需修改,只改上面的配置即可)
    const svg = `
    <svg xmlns="http://www.w3.org/2000/svg" width="${gapX}" height="${gapY}" style="opacity:${opacity}">
        <text x="0" y="50" font-size="${fontSize}" fill="#000" transform="rotate(${angle}, 0, 50)">
        ${watermarkText}
        </text>
    </svg>
    `;
    // 转 base64 格式,避免跨域问题
    const base64 = btoa(unescape(encodeURIComponent(svg)));
    return `data:image/svg+xml;base64,${base64}`;
}

// 创建并挂载水印遮罩层(核心容器)
function initWatermark() {
    // 先删除旧水印,避免重复生成
    const oldWm = document.getElementById('__watermark_protect');
    if (oldWm) oldWm.remove();

    // 创建水印容器
    const wmDiv = document.createElement('div');
    wmDiv.id = '__watermark_protect'; // 水印唯一标识,用于监听
    // 关键样式:全屏固定、最高层级、不影响点击
    wmDiv.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 9999999; /* 最高层级,避免被其他元素覆盖 */
    pointer-events: none; /* 灵魂属性:不阻挡页面点击、输入 */
    background-image: url(${createWatermark()});
    background-repeat: repeat; /* 平铺水印 */
    `;

    // 挂载到页面 body 中
    document.body.appendChild(wmDiv);
    return wmDiv;
}

// MutationObserver 监听防篡改
function observeWatermark() {
    const wmDiv = initWatermark(); // 初始化水印

    // 监听配置:监听哪些 DOM 变化
    const config = {
    childList: true,    // 监听子节点增删(比如水印被删除)
    attributes: true,   // 监听属性修改(比如样式被改)
    subtree: true,      // 监听整个子树变化(避免漏监)
    attributeOldValue: true, // 记录属性修改前的值(可选,用于复杂判断)
    };

    // 监听回调:一旦检测到变化,执行修复逻辑
    const callback = (mutationsList) => {
    // 遍历所有变化,判断是否是水印被破坏
    for (let mutation of mutationsList) {
        const currentWm = document.getElementById('__watermark_protect');
        // 触发修复的条件:水印节点不存在,或节点被替换
        if (!currentWm || currentWm !== wmDiv) {
        observeWatermark(); // 重新初始化水印+监听
        break; // 跳出循环,避免重复触发
        }
    }
    };

    // 创建监听器,监听 body 下的所有变化
    const observer = new MutationObserver(callback);
    observer.observe(document.body, config);
}

// 启动防篡改水印(页面加载完成后执行)
window.onload = observeWatermark;

优化建议

这个方案从原理上来说是绝对的防篡改的,但是也不是万能的。

它监听DOM的变化,所以DOM上的操作基本上都破解不了这个水印。

但是如果通过关闭 Js,然后去掉水印,这个防不住。

或者是抓包 的形式,以及通过注入代码 禁用掉 MutationObserver,这种都是防不住的。

所以系统级别 的水印可以用这个,但是文件级别的我还是推荐后端生成。

也就是说后端回传的PDF本身就是带着水印来的,这样能最大限度仿破解。

总结

MutationObserver 的核心原理就是通过监听 DOM 变化,水印被破坏就自动重建,实现前端层面的防篡改。

但是要记住的是:天底下没有绝对安全的系统

相关推荐
Beginner x_u2 小时前
前端八股整理(工程化 01)|Git 常见命令、rebase/merge、pull/fetch 与前端性能优化
前端·性能优化·git 常见命令
白日梦想家6812 小时前
实战避坑+性能对比,for与each循环选型指南
开发语言·前端·javascript
帅帅哥的兜兜2 小时前
猪齿鱼:实现table分页勾选
前端·javascript·vue.js
wicb91wJ62 小时前
手写一个Promise,彻底掌握异步原理
开发语言·前端·javascript
上海云盾-小余2 小时前
Web 业务常见 SQL 注入攻击原理详解及 WAF 防护部署实战教程
前端·数据库·sql
zs宝来了2 小时前
Next.js SSR/SSG:路由与渲染模式深度解析
前端·javascript·框架
ZC跨境爬虫2 小时前
UI前端美化技能提升日志day5:从布局优化到CSS继承原理深度解析
前端·css·ui·html·状态模式
易生一世2 小时前
Kiro CLI的context详解
前端
huangql5202 小时前
CSS布局(六):Grid —— 像围棋一样布局
前端·css