跳到主要内容

水印实现与防篡改

场景

后台管理系统需要添加用户信息水印,防止截图泄露。要求水印不能被轻易删除或篡改。

方案设计

Canvas 全屏水印

watermark.ts
interface WatermarkOptions {
text: string;
fontSize?: number;
color?: string;
rotate?: number;
gap?: number;
}

function createWatermark(options: WatermarkOptions): () => void {
const {
text,
fontSize = 16,
color = 'rgba(0, 0, 0, 0.05)',
rotate = -22,
gap = 200,
} = options;

// 1. 绘制单个水印 tile
const canvas = document.createElement('canvas');
canvas.width = gap;
canvas.height = gap;
const ctx = canvas.getContext('2d')!;
ctx.font = `${fontSize}px sans-serif`;
ctx.fillStyle = color;
ctx.rotate((rotate * Math.PI) / 180);
ctx.fillText(text, 0, gap / 2);

// 2. 创建水印容器
const watermarkDiv = document.createElement('div');
watermarkDiv.style.cssText = `
position: fixed; inset: 0; z-index: 9999;
pointer-events: none;
background: url(${canvas.toDataURL()}) repeat;
`;
document.body.appendChild(watermarkDiv);

// 3. MutationObserver 防删除/篡改
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// 水印节点被删除 → 重新插入
if ([...mutation.removedNodes].includes(watermarkDiv)) {
document.body.appendChild(watermarkDiv);
}
// 水印属性被修改 → 重新设置
if (mutation.target === watermarkDiv) {
watermarkDiv.style.cssText = `
position: fixed; inset: 0; z-index: 9999;
pointer-events: none;
background: url(${canvas.toDataURL()}) repeat;
`;
}
}
});
observer.observe(document.body, { childList: true });
observer.observe(watermarkDiv, { attributes: true, attributeFilter: ['style'] });

// 4. 返回销毁函数
return () => {
observer.disconnect();
watermarkDiv.remove();
};
}

暗水印(不可见水印)

通过修改图片像素 LSB(最低有效位)嵌入信息,肉眼不可见但可通过算法提取。

lsb-watermark.ts(原理示意)
function embedLSB(imageData: ImageData, message: string): ImageData {
const bits = message.split('').flatMap((c) =>
c.charCodeAt(0).toString(2).padStart(8, '0').split('').map(Number)
);

for (let i = 0; i < bits.length; i++) {
// 修改 R 通道最低位
imageData.data[i * 4] = (imageData.data[i * 4] & 0xfe) | bits[i];
}
return imageData;
}

常见面试问题

Q1: 水印实现有哪些方案?各有什么优缺点?

答案

方案优点缺点
CSS background-repeat简单容易被 DevTools 删除
Canvas → dataURL 背景灵活、防伪同上,需配合防篡改
Canvas 绘制在内容上强安全影响交互需 pointer-events: none
SVG 水印高清、可缩放同样需要防删除
暗水印(LSB)不可见、事后追溯实现复杂、截图可能丢失

Q2: 如何防止用户通过 DevTools 删除水印?

答案

  1. MutationObserver:监听水印节点的删除和属性修改,自动恢复
  2. Shadow DOM:将水印放在 Shadow DOM 中,增加操作难度
  3. 多层防御:CSS + Canvas + 暗水印组合使用
  4. 后端渲染:关键页面直接在服务端将水印嵌入图片
注意

前端水印无法做到 100% 防篡改,专业用户总能绕过。核心目的是提高篡改成本事后追溯

相关链接