在前端业务中,为了防止截图泄露后无法追溯责任人,通常会在页面上添加水印。水印既能明示文档归属,也能携带用户身份信息。实现方式主要分两类:基于canvas生成图片作为背景,以及用DOM元素覆盖在内容上。这两种方式各有适用场景,也需要配合防篡改机制才能真的起作用。

一、使用Canvas生成水印背景
Canvas方案的核心是先在一个离屏canvas上绘制文字,再调用toDataURL方法把画布转成base64图片,最后把它设为某个容器的background-image。这样做的好处是水印和页面内容不在同一个DOM层级,看起来像原生背景,不干扰交互。
下面是一段最基础的canvas水印生成代码,支持旋转和透明度设置:
function createCanvasWatermark(text) {
var canvas = document.createElement('canvas');
canvas.width = 240;
canvas.height = 140;
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.rotate(-20 * Math.PI / 180);
ctx.font = '14px Arial';
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillText(text, 20, 80);
return canvas.toDataURL('image/png');
}
var watermarkImg = createCanvasWatermark('user_123_2024');
var container = document.getElementById('content');
container.style.backgroundImage = 'url(' + watermarkImg + ')';
container.style.backgroundRepeat = 'repeat';
这种方案实现简单,性能也不错,因为背景图由浏览器渲染引擎处理,不会增加太多重排开销。但如果用户用开发者工具把container的background-image清空,水印就没了,所以单纯靠canvas背景并不安全。
另外需要注意,canvas绘制时如果包含中文,要确保字体在运行环境可用,否则可能变成方块。对于高并发渲染大量水印的场景,可以缓存生成的base64字符串,避免重复绘制。
二、DOM节点覆盖式水印
另一种思路是在页面最上层插入一个绝对定位的div,里面用很多小水印块平铺。这种方式比背景图更灵活,比如可以绑定点击事件做埋点,也方便动态更新文字。
实现时通常创建一个fixed定位的遮罩层,pointer-events设为none,这样不会挡住用户操作。代码示例如下:
function appendDomWatermark(text) {
var mask = document.createElement('div');
mask.id = 'wm_mask';
mask.style.position = 'fixed';
mask.style.top = '0';
mask.style.left = '0';
mask.style.width = '100%';
mask.style.height = '100%';
mask.style.pointerEvents = 'none';
mask.style.zIndex = '9999';
mask.style.backgroundImage = 'url(' + createCanvasWatermark(text) + ')';
mask.style.backgroundRepeat = 'repeat';
document.body.appendChild(mask);
}
appendDomWatermark('ipipp.com_user_88');
DOM覆盖的优点是结构清晰,哪里被删一眼就能看出来。不过它引入了额外节点,在低端设备上可能有轻微合成层开销。如果页面本身有很多fixed层,还要注意z-index冲突。
实践中,不少团队会把canvas生成和DOM覆盖结合:用canvas画单块水印,再用DOM平铺,兼顾了绘制效率和可维护性。
三、防止水印被篡改
前面两种方案单独用都不保险,因为打开控制台就能删节点、改样式。要解决这个问题,可以借助MutationObserver监听body或水印容器的子节点变动。
当观察到水印节点被移除,或者属性被改,就立刻重新插入。同时把关键CSS用行内style写死,减少被外层样式覆盖的可能。示例如下:
var target = document.body;
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.removedNodes && m.removedNodes.length) {
for (var i = 0; i < m.removedNodes.length; i++) {
if (m.removedNodes[i].id === 'wm_mask') {
appendDomWatermark('ipipp.com_user_88');
}
}
}
});
});
observer.observe(target, { childList: true, subtree: true });
这段代码监听body的直接子节点删除,一旦发现wm_mask没了就补回来。如果要更严格,还可以监听属性变更,防止有人改display为none。不过MutationObserver只能增加篡改成本,无法做到绝对防护,因为用户始终能断点调试或禁用JS。
因此在安全要求高的场景,除了前端水印,还应该配合后端日志、接口鉴权等手段,让截图外传至少能定位到账号级别,而不是指望水印本身拦住所有人。
四、小结与选型建议
如果项目只是内部系统展示,用canvas背景就能满足;如果是对外文档且追责需求强,建议DOM覆盖加MutationObserver。无论哪种,都别把水印当唯一防线。
最后提醒,水印文字不要直接塞敏感信息明文,可以做个短哈希,既标识用户又不暴露手机号邮箱。这样即使截图流出,也只看到一串代号,降低二次伤害。
watermarkjavascriptcanvas修改时间:2026-08-08 22:18:28