水印功能的核心作用
在前端实现水印功能是一个常见的需求,可以用于保护内容不被未经授权的复制和分发。水印可以是文本、图像或其他形式的标记,通常放置在页面的背景或内容上。
实现方式:
1. 实现文本水印
文本水印是最常见的水印形式,通常用于保护网页内容。
1.1 使用 CSS 实现文本水印(css+定位)
通过 CSS 的 ::before
或 ::after
伪元素来实现文本水印。
示例代码:
1. pointer-events: none
的作用
pointer-events: none;
是一个CSS属性,用于控制元素是否可以成为鼠标事件的目标。具体来说,当一个元素设置了 pointer-events: none;
后,该元素将不会响应任何鼠标事件,包括点击、悬停、拖动等。这意味着用户无法与该元素进行交互。
2. 具体效果
- 点击事件:用户无法点击该元素。
- 悬停事件:鼠标悬停在该元素上时,不会触发任何悬停效果(如改变鼠标指针形状、显示提示信息等)。
- 拖动事件:用户无法拖动该元素。
- 其他事件:所有与鼠标相关的事件(如
mousedown
、mouseup
、mousemove
等)都不会在该元素上触发。
3. 使用场景
pointer-events: none;
通常用于以下场景:
- 禁用交互:当需要临时禁用某个元素的交互功能时,可以使用
pointer-events: none;
。 - 覆盖层:在某些情况下,可能需要在页面上覆盖一层半透明的元素,但又不希望该层影响底层元素的交互,这时可以使用
pointer-events: none;
。 - 视觉效果:在某些视觉效果中,可能需要显示一个元素但不希望用户与其交互。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文本水印示例</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
position: relative;
}
.watermark {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
//属性用于禁用元素的鼠标事件,这意味着用户无法与该元素进行交互(如点击、悬停等)。在某些情况下,这可能是有意为之,但在其他情况下,这可能会导致用户界面的问题,使得某些功能无法正常使用。
pointer-events: none; /* 防止水印干扰用户交互 */
background-repeat: repeat;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><text x="50%" y="50%" font-family="Arial" font-size="20" fill="rgba(0, 0, 0, 0.1)" text-anchor="middle" dominant-baseline="middle" transform="rotate(-45, 100, 100)">水印文本</text></svg>');
}
</style>
</head>
<body>
<div class="watermark"></div>
<h1>欢迎访问我们的网站</h1>
<p>这是带有水印的示例页面。</p>
</body>
</html>
1.2 使用 JavaScript 动态生成水印
通过 JavaScript 动态生成水印,可以更灵活地控制水印内容和样式。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动态文本水印示例</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
position: relative;
}
.watermark {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
pointer-events: none; /* 防止水印干扰用户交互 */
background-repeat: repeat;
}
</style>
</head>
<body>
<div id="watermarkContainer"></div>
<h1>欢迎访问我们的网站</h1>
<p>这是带有水印的示例页面。</p>
<script>
function createWatermark(text) {
const watermarkContainer = document.getElementById('watermarkContainer');
//使用document.createElementNS创建一个SVG元素。设置宽高为200x200,并定义viewBox以控制缩放比例
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '200');
svg.setAttribute('height', '200');
svg.setAttribute('viewBox', '0 0 200 200');
//创建一个<text>元素,设置其位置为SVG中心(x='50%',y='50%')。设置字体、大小、颜色(半透明黑色)、对齐方式(居中)。使用transform属性将文本旋转-45°,形成倾斜效果。
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textElement.setAttribute('x', '50%');
textElement.setAttribute('y', '50%');
textElement.setAttribute('font-family', 'Arial');
textElement.setAttribute('font-size', '20');
textElement.setAttribute('fill', 'rgba(0, 0, 0, 0.1)');
textElement.setAttribute('text-anchor', 'middle');
textElement.setAttribute('dominant-baseline', 'middle');
textElement.setAttribute('transform', 'rotate(-45, 100, 100)');
textElement.textContent = text;
svg.appendChild(textElement);
//使用XMLSerializer将SVG元素转换为字符串。使用btoa方法将字符串编码为Base64格式。将Base64字符串嵌入到data:image/svg+xml;base64,...中,生成图片URL。
const svgString = new XMLSerializer().serializeToString(svg);
const base64 = btoa(svgString);
const url = `data:image/svg+xml;base64,${base64}`;
//将生成的图片URL设置为watermarkContainer的backgroundImage属性,从而实现动态水印效果。
watermarkContainer.style.backgroundImage = `url(${url})`;
}
createWatermark('水印文本');
</script>
</body>
</html>
1.3 canvas动态生成水印
function createWatermark(text = 'Watermark') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸
canvas.width = 400;
canvas.height = 200;
// 绘制水印
ctx.font = '20px Arial';
ctx.fillStyle = 'rgba(100, 100, 100, 0.2)';
ctx.rotate(-Math.PI / 6); // 旋转-30度
ctx.fillText(text, 50, 150);
// 生成 Base64 背景图
return canvas.toDataURL('image/png');
}
// 应用水印样式
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: url(${createWatermark()}) repeat;
}
`;
document.head.appendChild(style);
2 MutationObserver 防删水印代码
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.removedNodes.length) {
const removed = Array.from(mutation.removedNodes);
if (removed.some(node => node.classList?.contains('watermark'))) {
document.body.appendChild(createWatermarkElement());
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
分步解析
1. 创建 MutationObserver 实例
const observer = new MutationObserver((mutations) => { ... });
- 作用:初始化一个 DOM 变化观察器,用于监听指定节点的变化
- 回调参数:
mutations
是所有变更记录的数组
2. 遍历所有变更记录
mutations.forEach((mutation) => { ... });
-
mutation
对象:包含以下关键属性:type
:变更类型(如childList
、attributes
)removedNodes
:被移除的节点列表(NodeList
)addedNodes
:新增的节点列表
3. 检测是否有节点被移除
if (mutation.removedNodes.length) {
// 处理被删除的节点
}
- 逻辑:仅当有节点被移除时才进入处理
4. 转换节点列表为数组
const removed = Array.from(mutation.removedNodes);
- 原因:
removedNodes
是类数组对象NodeList
,转换为数组便于使用Array.some()
方法 - 等效代码:
[...mutation.removedNodes]
5. 检查被删节点是否包含水印
removed.some(node => node.classList?.contains('watermark'))
- 方法:
Array.some()
检测数组中是否有元素满足条件 - 安全操作:
node.classList?.contains()
使用可选链避免null.classList
报错 - 目标:确认被删除节点中存在
class="watermark"
的元素
6. 重新插入水印
document.body.appendChild(createWatermarkElement());
- 恢复逻辑:若水印被删除,立即调用
createWatermarkElement()
创建新水印并插入到<body>
末尾 - 注意:需提前定义
createWatermarkElement()
方法(生成水印 DOM 元素)
7. 启动观察器
observer.observe(document.body, {
childList: true,
subtree: true
});
- 配置项:
childList: true
:监听目标节点子节点的添加/移除subtree: true
:监听目标节点所有后代节点的变化
- 监控范围:
document.body
及其所有子节点的变更
方案对比
方案 | 优点 | 缺点 |
---|---|---|
CSS 绝对定位 | 实现简单、性能高 | 易被开发者工具删除 |
Canvas 背景图 | 难以直接删除、支持复杂样式 | 内存占用稍高 |
防篡改监控 | 增强安全性 | 增加性能开销 |
服务端生成 | 安全性最高 | 需后端配合、增加服务器负载 |