实现一个简单的水印效果

777 阅读3分钟

通过 JavaScript,我们可以创建一个网页水印功能,并利用 MutationObserver API 监控 DOM 变化,防止水印被删除。以下是一个完整的实现示例:

功能特点:

  1. 动态添加水印。
  2. 使用 MutationObserver 监控水印是否被删除或修改,若有变化,则立即恢复。

1. DOM实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Page Watermark</title>
  <style>
    #watermark {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none; /* 确保水印不影响页面交互 */
      z-index: 9999;
    }

    .watermark-text {
      position: absolute;
      color: rgba(0, 0, 0, 0.1); /* 半透明颜色 */
      font-size: 20px;
      transform: rotate(-30deg);
      user-select: none; /* 禁止选中 */
      white-space: nowrap;
    }
  </style>
</head>
<body>
  <h1>网页水印示例</h1>
  <p>这是一个展示带有防清除功能水印的网页。</p>

  <script>
    // 创建水印函数
    function createWatermark(text) {
      // 如果水印已存在,先删除
      const existingWatermark = document.getElementById('watermark');
      if (existingWatermark) {
        existingWatermark.remove();
      }

      // 创建水印容器
      const watermarkContainer = document.createElement('div');
      watermarkContainer.id = 'watermark';
      // 文档碎片
      const fragment = document.createDocumentFragment()
      // 计算行列数
      const cols = Math.ceil(window.innerWidth / 200);
      const rows = Math.ceil(window.innerHeight / 100);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          const watermarkText = document.createElement('div');
          watermarkText.className = 'watermark-text';
          watermarkText.innerText = text;
          watermarkText.style.top = `${i * 100}px`;
          watermarkText.style.left = `${j * 200}px`;
          fragment.appendChild(watermarkText)
        }
      }
      watermarkContainer.appendChild(fragment);
      document.body.appendChild(watermarkContainer);
    }

    // 初始化水印
    createWatermark('Confidential');

    // 使用 MutationObserver 监听水印是否被删除
    const observer = new MutationObserver(() => {
      const watermark = document.getElementById('watermark');
      if (!watermark) {
        createWatermark('Confidential');
      }
    });

    // 监听整个 body
    observer.observe(document.body, {
      childList: true, // 监控子节点的变化
      subtree: true,  // 监控所有子树
    });

    // 监听窗口大小变化,动态调整水印
    window.addEventListener('resize', () => {
      createWatermark('Confidential');
    });
  </script>
</body>
</html>

2. canvas实现

<style>
    body {
      font-family: Arial, sans-serif;
    }

    #watermarkCanvas {
      position: absolute;
      top: 0;
      left: 0;
      pointer-events: none; /* 确保水印不影响页面交互 */
      z-index: 9999;
    }
  </style>
   <script>
    let watermarkCanvas = null;
    let watermarkCtx = null;

    // 创建 Canvas 水印函数
    function createWatermark(text) {
      if (!watermarkCanvas) {
        watermarkCanvas = document.createElement('canvas');
        watermarkCanvas.id = 'watermarkCanvas';
        document.body.appendChild(watermarkCanvas);
      }
      const canvasWidth = window.innerWidth;  // 计算 Canvas 宽度
      const canvasHeight = window.innerHeight; // 计算 Canvas 高度
      if (watermarkCanvas.width !== canvasWidth || watermarkCanvas.height !== canvasHeight) {
        watermarkCanvas.width = canvasWidth;
        watermarkCanvas.height = canvasHeight;

        // 设置 canvas 绘制的真实大小
        watermarkCanvas.width = window.innerWidth
        watermarkCanvas.height = window.innerHeight
        watermarkCtx = watermarkCanvas.getContext('2d');
      }

      watermarkCtx.clearRect(0, 0, watermarkCanvas.width, watermarkCanvas.height); // 清除之前的内容

      watermarkCtx.globalAlpha = 0.1; // 使用 globalAlpha 确保透明度设置的一致性
      watermarkCtx.fillStyle = 'rgba(0, 0, 0, 1)'; // 设置填充颜色,透明度通过 globalAlpha 控制
      watermarkCtx.font = '24px sans-serif'; // 设置字体大小
      watermarkCtx.textAlign = 'center';
      watermarkCtx.textBaseline = 'middle';

      const rowSpacing = 120; // 行间距
      const colSpacing = 100; // 列间距
      const rows = Math.ceil(watermarkCanvas.height / rowSpacing); // 行数
      const cols = Math.ceil(watermarkCanvas.width / colSpacing); // 列数

      // 绘制水印
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          watermarkCtx.save(); // 保存当前状态
          watermarkCtx.translate(j * colSpacing + 100, i * rowSpacing + 50); // 平移到正确的位置
          watermarkCtx.rotate(-30 * Math.PI / 180); // 旋转 -30 度
          watermarkCtx.fillText(text, 0, 0); // 绘制水印文本
          watermarkCtx.restore(); // 恢复之前的状态
        }
      }
    }

    // 初始化水印
    createWatermark('机密文件');

    // 使用 MutationObserver 监听水印是否被删除
    const observer = new MutationObserver(() => {
      if (!document.getElementById('watermarkCanvas')) {
        createWatermark('机密文件');
      }
    });

    // 监听整个 body
    observer.observe(document.body, {
      childList: true, // 监控子节点的变化
      subtree: true,  // 监控所有子树
    });

    // 监听窗口大小变化,动态调整水印
    window.addEventListener('resize', () => {
      createWatermark('机密文件');
    });
  </script>

代码说明:

  1. 水印绘制

    • 水印通过 div 元素生成,pointer-events: none 确保不会影响页面操作。
    • 每行间隔和列间隔可根据窗口大小动态计算。
    • 使用 rgba 半透明颜色和旋转效果,确保水印清晰可见但不干扰内容。
  2. 防清除功能

    • 使用 MutationObserver 监听 body 元素的子节点变化。
    • 如果水印被删除,自动重新添加水印。
  3. 窗口大小调整

    • 监听 resize 事件,确保在调整窗口时,水印布局自动更新。

测试方法:

  1. 打开页面后,尝试手动从 DOM 中删除水印(在控制台执行 document.getElementById('watermark').remove())。
  2. 观察水印是否会重新出现。
  3. 调整窗口大小,水印应自动调整布局。

注意事项:

  • 无法完全防止清除:如果恶意用户禁用了 JavaScript,或者通过高级方式清空 DOM,再恢复功能会受到限制。
  • 提高复杂性:可以混淆或动态生成水印代码,增加清除难度。