javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > 前端网页水印及防移除

前端实现网页水印及防移除的多种方案

作者:北辰alk

‌在前端实现水印功能是一个常见的需求,可以用于保护内容不被未经授权的复制和分发,水印可以是文本、图像或其他形式的标记,通常放置在页面的背景或内容上,本文将详细介绍前端实现网页水印的多种方法,并探讨如何防止水印被轻易移除的技术方案

一、前言

在Web开发中,保护内容版权和防止信息泄露变得越来越重要。为网页添加水印是一种常见的内容保护手段,可以用于标识内容来源、追踪泄露渠道或声明版权。本文将详细介绍前端实现网页水印的多种方法,并探讨如何防止水印被轻易移除的技术方案。

二、网页水印的基本实现原理

网页水印的基本原理是在页面内容之上叠加一层半透明的文字或图片,使其不影响用户正常浏览,但又能清晰可见。实现方式主要包括:

三、基础水印实现方案

3.1 Canvas绘制水印

Canvas是HTML5提供的绘图API,非常适合用于生成动态水印。

function createWatermark(text) {
  const canvas = document.createElement('canvas');
  canvas.width = 300;
  canvas.height = 200;
  const ctx = canvas.getContext('2d');
  
  ctx.font = '16px Arial';
  ctx.fillStyle = 'rgba(200, 200, 200, 0.3)';
  ctx.rotate(-20 * Math.PI / 180);
  ctx.fillText(text, 50, 100);
  
  return canvas.toDataURL('image/png');
}

function applyWatermark() {
  const watermarkUrl = createWatermark('机密文档 严禁外传');
  const watermarkDiv = document.createElement('div');
  watermarkDiv.style.position = 'fixed';
  watermarkDiv.style.top = '0';
  watermarkDiv.style.left = '0';
  watermarkDiv.style.width = '100%';
  watermarkDiv.style.height = '100%';
  watermarkDiv.style.pointerEvents = 'none';
  watermarkDiv.style.backgroundImage = `url(${watermarkUrl})`;
  watermarkDiv.style.zIndex = '9999';
  
  document.body.appendChild(watermarkDiv);
}

window.onload = applyWatermark;

3.2 CSS背景图水印

使用CSS的background-image属性可以轻松实现全屏水印效果。

body {
  position: relative;
}

body::after {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
  pointer-events: none;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"><text x="50" y="100" font-family="Arial" font-size="16" fill="rgba(200,200,200,0.3)" transform="rotate(-20)">机密文档 严禁外传</text></svg>');
  background-repeat: repeat;
}

3.3 SVG水印

SVG水印具有矢量特性,缩放不失真。

function createSvgWatermark(text) {
  const svg = `
    <svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
      <text x="50" y="100" font-family="Arial" font-size="16" 
        fill="rgba(200,200,200,0.3)" transform="rotate(-20)">
        ${text}
      </text>
    </svg>`;
  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}

function applySvgWatermark() {
  const watermarkUrl = createSvgWatermark('机密文档 严禁外传');
  const style = document.createElement('style');
  style.innerHTML = `
    body::after {
      content: "";
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 9999;
      pointer-events: none;
      background-image: url("${watermarkUrl}");
      background-repeat: repeat;
    }
  `;
  document.head.appendChild(style);
}

3.4 DOM元素覆盖水印

使用大量小DOM元素平铺形成水印,增加移除难度。

function createDomWatermark(text) {
  const container = document.createElement('div');
  container.style.position = 'fixed';
  container.style.top = '0';
  container.style.left = '0';
  container.style.width = '100%';
  container.style.height = '100%';
  container.style.zIndex = '9999';
  container.style.pointerEvents = 'none';
  container.style.overflow = 'hidden';
  
  for (let i = 0; i < 200; i++) {
    const watermark = document.createElement('div');
    watermark.textContent = text;
    watermark.style.position = 'absolute';
    watermark.style.color = 'rgba(200, 200, 200, 0.3)';
    watermark.style.fontSize = '16px';
    watermark.style.transform = 'rotate(-20deg)';
    watermark.style.userSelect = 'none';
    
    const x = Math.random() * window.innerWidth;
    const y = Math.random() * window.innerHeight;
    watermark.style.left = `${x}px`;
    watermark.style.top = `${y}px`;
    
    container.appendChild(watermark);
  }
  
  document.body.appendChild(container);
}

四、水印防移除技术

基础水印容易被开发者工具移除,我们需要增加防护措施。

4.1 防删除机制

function protectWatermark() {
  const watermark = document.getElementById('watermark');
  
  // 监控DOM变化
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.removedNodes.forEach((node) => {
        if (node === watermark) {
          document.body.appendChild(watermark);
        }
      });
    });
  });
  
  observer.observe(document.body, {
    childList: true,
    subtree: true
  });
  
  // 定期检查水印是否存在
  setInterval(() => {
    if (!document.contains(watermark)) {
      document.body.appendChild(watermark);
    }
  }, 1000);
  
  // 防止控制台修改样式
  Object.defineProperty(watermark.style, 'display', {
    set: function() {},
    get: function() { return 'block'; }
  });
}

4.2 防隐藏机制

function preventHideWatermark() {
  const watermark = document.getElementById('watermark');
  
  // 监控样式变化
  const styleObserver = new MutationObserver(() => {
    if (watermark.style.display === 'none' || 
        watermark.style.visibility === 'hidden' ||
        watermark.style.opacity === '0') {
      watermark.style.display = 'block';
      watermark.style.visibility = 'visible';
      watermark.style.opacity = '1';
    }
  });
  
  styleObserver.observe(watermark, {
    attributes: true,
    attributeFilter: ['style']
  });
  
  // 防止透明度被修改
  Object.defineProperty(watermark.style, 'opacity', {
    set: function(value) {
      if (parseFloat(value) < 0.1) {
        this._opacity = '0.3';
      } else {
        this._opacity = value;
      }
    },
    get: function() {
      return this._opacity || '0.3';
    }
  });
}

4.3 动态水印技术

function dynamicWatermark(userInfo) {
  const canvas = document.createElement('canvas');
  canvas.width = 300;
  canvas.height = 200;
  const ctx = canvas.getContext('2d');
  
  // 绘制基础水印
  ctx.font = '16px Arial';
  ctx.fillStyle = 'rgba(200, 200, 200, 0.3)';
  ctx.rotate(-20 * Math.PI / 180);
  ctx.fillText('机密文档 严禁外传', 50, 100);
  
  // 添加用户特定信息
  ctx.font = '12px Arial';
  ctx.fillText(`用户: ${userInfo.name}`, 50, 130);
  ctx.fillText(`时间: ${new Date().toLocaleString()}`, 50, 150);
  
  // 应用水印
  const watermarkDiv = document.createElement('div');
  watermarkDiv.id = 'watermark';
  watermarkDiv.style.position = 'fixed';
  watermarkDiv.style.top = '0';
  watermarkDiv.style.left = '0';
  watermarkDiv.style.width = '100%';
  watermarkDiv.style.height = '100%';
  watermarkDiv.style.pointerEvents = 'none';
  watermarkDiv.style.backgroundImage = `url(${canvas.toDataURL('image/png')})`;
  watermarkDiv.style.zIndex = '9999';
  
  document.body.appendChild(watermarkDiv);
  
  // 定期更新水印
  setInterval(() => {
    dynamicWatermark(userInfo);
  }, 60000); // 每分钟更新一次
}

4.4 服务端配合的水印方案

前端水印容易被绕过,结合服务端可以增强安全性。

// 前端代码
async function getWatermarkImage(userId) {
  const response = await fetch(`/api/watermark?userId=${userId}`);
  const blob = await response.blob();
  return URL.createObjectURL(blob);
}

async function applyServerWatermark() {
  const userId = getCurrentUserId(); // 获取当前用户ID
  const watermarkUrl = await getWatermarkImage(userId);
  
  const watermarkDiv = document.createElement('div');
  watermarkDiv.style.position = 'fixed';
  watermarkDiv.style.top = '0';
  watermarkDiv.style.left = '0';
  watermarkDiv.style.width = '100%';
  watermarkDiv.style.height = '100%';
  watermarkDiv.style.pointerEvents = 'none';
  watermarkDiv.style.backgroundImage = `url(${watermarkUrl})`;
  watermarkDiv.style.zIndex = '9999';
  
  document.body.appendChild(watermarkDiv);
}

// 服务端示例(Node.js)
app.get('/api/watermark', (req, res) => {
  const { userId } = req.query;
  const canvas = createCanvas(300, 200);
  const ctx = canvas.getContext('2d');
  
  // 绘制水印
  ctx.font = '16px Arial';
  ctx.fillStyle = 'rgba(200, 200, 200, 0.3)';
  ctx.rotate(-20 * Math.PI / 180);
  ctx.fillText('机密文档 严禁外传', 50, 100);
  ctx.fillText(`用户ID: ${userId}`, 50, 130);
  ctx.fillText(`时间: ${new Date().toISOString()}`, 50, 150);
  
  // 返回图片
  const buffer = canvas.toBuffer('image/png');
  res.set('Content-Type', 'image/png');
  res.send(buffer);
});

五、高级防护方案

5.1 屏幕录制防护

function preventScreenCapture() {
  // 检测常见录屏软件
  const detectScreenCapture = () => {
    const perf = window.performance || window.webkitPerformance;
    const entries = perf?.getEntries() || [];
    const screenCaptureApps = ['bandicam', 'obs', 'camtasia', 'screencast'];
    
    for (const entry of entries) {
      if (screenCaptureApps.some(app => entry.name.toLowerCase().includes(app))) {
        document.body.innerHTML = '<h1>检测到屏幕录制软件,内容保护已启用</h1>';
        return true;
      }
    }
    return false;
  };
  
  // 检测DevTools开启
  const detectDevTools = () => {
    const devtools = /./;
    devtools.toString = function() {
      document.body.innerHTML = '<h1>开发者工具已禁用</h1>';
      return '';
    };
    console.log('%c', devtools);
  };
  
  // 定期检查
  setInterval(() => {
    detectScreenCapture();
    detectDevTools();
  }, 1000);
  
  // 禁止右键菜单和快捷键
  document.addEventListener('contextmenu', e => e.preventDefault());
  document.addEventListener('keydown', e => {
    if (e.ctrlKey && (e.key === 'u' || e.key === 's' || e.key === 'c')) {
      e.preventDefault();
    }
    if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key === 'I')) {
      e.preventDefault();
    }
  });
}

5.2 数字水印技术

数字水印将信息隐藏在视觉不可见的像素中。

function embedDigitalWatermark(imageElement, watermarkText) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = imageElement.width;
  canvas.height = imageElement.height;
  ctx.drawImage(imageElement, 0, 0);
  
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  
  // 将文本转换为二进制
  const binaryText = watermarkText.split('').map(char => 
    char.charCodeAt(0).toString(2).padStart(8, '0')).join('');
  
  // 在每个像素的最低有效位嵌入1位信息
  for (let i = 0; i < binaryText.length; i++) {
    const pixelIndex = i * 4;
    if (pixelIndex >= data.length) break;
    
    // 修改蓝色通道的最低有效位
    data[pixelIndex + 2] = (data[pixelIndex + 2] & 0xFE) | parseInt(binaryText[i]);
  }
  
  ctx.putImageData(imageData, 0, 0);
  imageElement.src = canvas.toDataURL('image/png');
}

// 提取数字水印
function extractDigitalWatermark(imageElement, length) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = imageElement.width;
  canvas.height = imageElement.height;
  ctx.drawImage(imageElement, 0, 0);
  
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  let binaryText = '';
  
  for (let i = 0; i < length * 8; i++) {
    const pixelIndex = i * 4;
    if (pixelIndex >= data.length) break;
    
    // 提取蓝色通道的最低有效位
    binaryText += (data[pixelIndex + 2] & 1).toString();
  }
  
  // 将二进制转换为文本
  let text = '';
  for (let i = 0; i < binaryText.length; i += 8) {
    const byte = binaryText.substr(i, 8);
    text += String.fromCharCode(parseInt(byte, 2));
  }
  
  return text;
}

六、水印实现流程图

七、最佳实践建议

八、总结

网页水印的实现和防护是一个不断演进的技术领域。本文介绍了从基础到高级的多种水印实现方案,以及相应的防移除技术。需要注意的是,没有任何前端水印方案是绝对安全的,但通过组合多种技术可以显著提高移除难度。对于高安全性要求的场景,建议结合服务端验证和数字水印等高级方案。

在实际应用中,应根据具体需求选择合适的水印方案,平衡安全性、用户体验和性能开销。随着Web技术的不断发展,水印技术也将持续演进,开发者需要保持对新技术的学习和关注。

以上就是前端实现网页水印及防移除的多种方案的详细内容,更多关于前端网页水印及防移除的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文