水印效果在前端页面中应用十分广泛,既可以用于版权保护,也可以标注内部测试、保密等页面属性。用JavaScript实现水印效果不需要依赖第三方库,核心思路是通过动态生成内容覆盖在页面上,同时避免影响原有页面的正常交互。

实现水印的两种核心思路
目前主流的JavaScript水印实现方式分为两种,开发者可以根据实际需求选择:
- Canvas绘制方式:通过canvas生成水印图片,再将图片作为背景平铺到页面,适合需要全屏覆盖、样式统一的场景。
- DOM节点插入方式:动态创建多个包含水印文本的DOM节点,设置固定定位覆盖在页面上,适合需要调整单个水印样式、支持简单交互的场景。
Canvas方式实现水印
Canvas方式的实现步骤是先创建canvas元素,绘制水印文本,再将canvas转换为图片作为背景,最后设置给目标容器。
完整实现代码
// 定义水印配置
const watermarkConfig = {
text: '内部测试', // 水印文本
fontSize: 16, // 字体大小
fontFamily: 'Microsoft YaHei', // 字体
color: 'rgba(0, 0, 0, 0.1)', // 字体颜色,半透明避免遮挡内容
rotate: -20, // 旋转角度,单位度
width: 200, // 单个水印区域宽度
height: 150, // 单个水印区域高度
container: document.body // 水印挂载的容器,默认是body
};
function createCanvasWatermark(config) {
const { text, fontSize, fontFamily, color, rotate, width, height, container } = config;
// 创建canvas元素
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 设置字体样式
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillStyle = color;
// 平移画布原点,方便旋转
ctx.translate(width / 2, height / 2);
// 旋转画布
ctx.rotate((rotate * Math.PI) / 180);
// 绘制文本,偏移让文本居中
ctx.fillText(text, -ctx.measureText(text).width / 2, fontSize / 2);
// 将canvas转换为图片URL
const watermarkUrl = canvas.toDataURL('image/png');
// 设置容器的背景为水印图片平铺
container.style.backgroundImage = `url(${watermarkUrl})`;
container.style.backgroundRepeat = 'repeat';
// 防止用户修改背景样式
const observer = new MutationObserver(() => {
if (container.style.backgroundImage !== `url(${watermarkUrl})`) {
container.style.backgroundImage = `url(${watermarkUrl})`;
container.style.backgroundRepeat = 'repeat';
}
});
observer.observe(container, { attributes: true, attributeFilter: ['style'] });
}
// 调用函数生成水印
createCanvasWatermark(watermarkConfig);DOM节点方式实现水印
DOM方式通过创建多个绝对定位的水印节点,平铺在容器上方,每个节点都可以单独控制样式,灵活性更高。
完整实现代码
// 定义水印配置
const domWatermarkConfig = {
text: '保密资料', // 水印文本
fontSize: 14,
color: 'rgba(100, 100, 100, 0.15)',
rotate: -15,
gapX: 150, // 水平方向水印间距
gapY: 120, // 垂直方向水印间距
container: document.body
};
function createDOMWatermark(config) {
const { text, fontSize, color, rotate, gapX, gapY, container } = config;
// 创建水印容器
const watermarkWrapper = document.createElement('div');
watermarkWrapper.style.position = 'fixed';
watermarkWrapper.style.top = '0';
watermarkWrapper.style.left = '0';
watermarkWrapper.style.width = '100%';
watermarkWrapper.style.height = '100%';
watermarkWrapper.style.pointerEvents = 'none'; // 避免遮挡页面点击等交互
watermarkWrapper.style.zIndex = '9999';
watermarkWrapper.style.overflow = 'hidden';
// 计算需要生成的水印数量
const containerWidth = container.offsetWidth || window.innerWidth;
const containerHeight = container.offsetHeight || window.innerHeight;
const colCount = Math.ceil(containerWidth / gapX) + 1;
const rowCount = Math.ceil(containerHeight / gapY) + 1;
// 循环生成水印节点
for (let i = 0; i < rowCount; i++) {
for (let j = 0; j < colCount; j++) {
const watermarkItem = document.createElement('div');
watermarkItem.innerText = text;
watermarkItem.style.position = 'absolute';
watermarkItem.style.left = `${j * gapX}px`;
watermarkItem.style.top = `${i * gapY}px`;
watermarkItem.style.fontSize = `${fontSize}px`;
watermarkItem.style.color = color;
watermarkItem.style.transform = `rotate(${rotate}deg)`;
watermarkItem.style.whiteSpace = 'nowrap';
watermarkWrapper.appendChild(watermarkItem);
}
}
container.appendChild(watermarkWrapper);
// 防篡改处理,监听水印容器是否被删除或修改
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.removedNodes.length > 0) {
for (let node of mutation.removedNodes) {
if (node === watermarkWrapper) {
container.appendChild(watermarkWrapper);
}
}
}
});
});
observer.observe(container, { childList: true });
}
// 调用函数生成水印
createDOMWatermark(domWatermarkConfig);两种方式的对比与选择
可以通过下面的表格快速判断哪种方式更适合自己的场景:
| 对比维度 | Canvas方式 | DOM节点方式 |
|---|---|---|
| 性能表现 | 只生成一个背景图,性能更好,适合大页面 | 生成多个DOM节点,节点过多时可能影响性能 |
| 样式灵活性 | 所有水印样式统一,无法单独调整单个水印 | 可以单独设置每个水印的样式,支持差异化展示 |
| 防篡改能力 | 背景图容易被开发者工具修改,需要额外监听 | 节点容易被删除,需要监听DOM变化 |
| 适用场景 | 全屏平铺、样式统一的水印需求 | 需要自定义单个水印、轻量场景 |
注意事项
实现水印效果时还需要注意几个问题:
- 水印的
z-index需要设置得足够高,避免被页面其他元素遮挡,但也不能过高影响弹窗等顶层组件。 - 设置水印容器的
pointer-events为none,避免水印遮挡页面的点击、滚动等正常交互。 - 如果页面内容动态加载,需要在内容加载完成后重新计算水印范围,避免水印覆盖不全。
- 防篡改逻辑可以根据需求调整,比如禁止打开开发者工具、检测水印被修改后重新生成等,提升水印的安全性。
JavaScript水印效果canvasDOM操作前端开发修改时间:2026-06-04 03:31:56