js实现水印效果的两种核心方案
在web开发中,使用js实现水印效果主要有两种常用思路,一种是通过canvas绘制水印内容生成图片,再将图片作为背景平铺到页面上;另一种是直接创建DOM元素,通过样式控制水印的位置和展示效果。两种方案各有适用场景,下面分别展开说明。
方案一:基于canvas生成水印背景
这种方案的核心是先使用canvas绘制出水印的文字内容,再将canvas转换为base64格式的图片,最后把这张图片设置为页面的背景并平铺,实现全页面水印覆盖。
实现步骤
- 创建canvas元素,设置画布的宽高
- 获取canvas的绘图上下文,设置文字的样式、颜色、旋转角度
- 绘制水印文字到canvas上
- 将canvas转换为base64图片地址
- 将图片地址设置为目标容器的背景,设置背景平铺
完整代码示例
// 生成水印的方法
function createCanvasWatermark(watermarkText, targetSelector = 'body') {
// 创建canvas元素
const canvas = document.createElement('canvas');
// 设置单个水印块的宽高
canvas.width = 200;
canvas.height = 150;
const ctx = canvas.getContext('2d');
// 设置文字样式
ctx.font = '16px Microsoft YaHei';
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
// 旋转画布,让文字倾斜
ctx.rotate(-20 * Math.PI / 180);
// 绘制水印文字
ctx.fillText(watermarkText, 10, 80);
// 转换为base64图片
const base64Url = canvas.toDataURL('image/png');
// 获取目标容器,设置背景
const targetDom = document.querySelector(targetSelector);
if (targetDom) {
targetDom.style.backgroundImage = `url(${base64Url})`;
targetDom.style.backgroundRepeat = 'repeat';
}
}
// 调用方法,设置水印内容
createCanvasWatermark('测试水印');
方案二:直接操作DOM创建水印元素
这种方案不需要生成图片,直接创建多个包含水印文字的div元素,通过绝对定位平铺到页面上,适合需要动态调整水印内容或者水印元素需要响应交互的场景。
实现步骤
- 计算页面可展示区域的大小,确定需要创建的水印元素数量
- 循环创建水印div元素,设置统一的样式,包括文字、颜色、旋转、定位
- 将所有水印元素添加到页面的容器中
完整代码示例
// 生成DOM水印的方法
function createDOMWatermark(watermarkText, targetSelector = 'body') {
const targetDom = document.querySelector(targetSelector);
if (!targetDom) return;
// 获取容器宽高
const containerWidth = targetDom.offsetWidth;
const containerHeight = targetDom.offsetHeight;
// 单个水印块的宽高
const watermarkWidth = 200;
const watermarkHeight = 150;
// 计算需要创建的水印行数和列数
const rowCount = Math.ceil(containerHeight / watermarkHeight);
const colCount = Math.ceil(containerWidth / watermarkWidth);
// 循环创建水印元素
for (let i = 0; i < rowCount; i++) {
for (let j = 0; j < colCount; j++) {
const watermarkItem = document.createElement('div');
// 设置水印样式
watermarkItem.style.position = 'absolute';
watermarkItem.style.left = j * watermarkWidth + 'px';
watermarkItem.style.top = i * watermarkHeight + 'px';
watermarkItem.style.width = watermarkWidth + 'px';
watermarkItem.style.height = watermarkHeight + 'px';
watermarkItem.style.fontSize = '16px';
watermarkItem.style.fontFamily = 'Microsoft YaHei';
watermarkItem.style.color = 'rgba(0, 0, 0, 0.1)';
watermarkItem.style.transform = 'rotate(-20deg)';
watermarkItem.style.pointerEvents = 'none';
watermarkItem.style.userSelect = 'none';
watermarkItem.innerHTML = watermarkText;
targetDom.appendChild(watermarkItem);
}
}
}
// 调用方法
createDOMWatermark('测试水印');
水印防篡改实现思路
上面的基础方案实现的水印容易被用户通过开发者工具删除,要实现防篡改效果,可以添加监听逻辑:
- 使用
MutationObserver监听水印容器或者水印元素的DOM变化,一旦发现水印被删除,立即重新生成水印 - 定时检测水印元素是否存在,不存在则重新创建
- 将水印元素的
pointer-events设置为none,避免影响页面正常交互
防篡改代码示例
// 给DOM水印添加防篡改逻辑
function addWatermarkProtect(targetSelector = 'body') {
const targetDom = document.querySelector(targetSelector);
if (!targetDom) return;
// 创建MutationObserver监听DOM变化
const observer = new MutationObserver(() => {
// 检测水印元素是否存在,这里假设水印元素的className为watermark-item
const watermarkList = targetDom.querySelectorAll('.watermark-item');
if (watermarkList.length === 0) {
// 水印被删除,重新生成
createDOMWatermark('测试水印', targetSelector);
}
});
// 开始监听目标容器的子节点变化
observer.observe(targetDom, {
childList: true,
subtree: true
});
}
// 修改createDOMWatermark方法,给每个水印元素添加className
function createDOMWatermark(watermarkText, targetSelector = 'body') {
const targetDom = document.querySelector(targetSelector);
if (!targetDom) return;
const containerWidth = targetDom.offsetWidth;
const containerHeight = targetDom.offsetHeight;
const watermarkWidth = 200;
const watermarkHeight = 150;
const rowCount = Math.ceil(containerHeight / watermarkHeight);
const colCount = Math.ceil(containerWidth / watermarkWidth);
for (let i = 0; i < rowCount; i++) {
for (let j = 0; j < colCount; j++) {
const watermarkItem = document.createElement('div');
watermarkItem.className = 'watermark-item';
watermarkItem.style.position = 'absolute';
watermarkItem.style.left = j * watermarkWidth + 'px';
watermarkItem.style.top = i * watermarkHeight + 'px';
watermarkItem.style.width = watermarkWidth + 'px';
watermarkItem.style.height = watermarkHeight + 'px';
watermarkItem.style.fontSize = '16px';
watermarkItem.style.fontFamily = 'Microsoft YaHei';
watermarkItem.style.color = 'rgba(0, 0, 0, 0.1)';
watermarkItem.style.transform = 'rotate(-20deg)';
watermarkItem.style.pointerEvents = 'none';
watermarkItem.style.userSelect = 'none';
watermarkItem.innerHTML = watermarkText;
targetDom.appendChild(watermarkItem);
}
}
// 添加防篡改监听
addWatermarkProtect(targetSelector);
}
// 调用方法
createDOMWatermark('测试水印');
两种方案的对比选择
| 对比维度 | canvas水印方案 | DOM水印方案 |
|---|---|---|
| 性能 | 只生成一张背景图,性能更好 | 需要创建多个DOM元素,数量多时性能略差 |
| 灵活性 | 水印内容修改需要重新生成canvas | 可以直接修改DOM元素内容,更灵活 |
| 防篡改难度 | 修改背景图需要重新生成,防篡改相对容易 | DOM元素容易被单独删除,需要额外监听逻辑 |
| 适用场景 | 全页面静态水印,不需要频繁修改水印内容 | 水印内容需要动态调整,或者需要单个水印响应交互 |
开发者可以根据项目的实际需求选择合适的js水印实现方案,两种方案的核心逻辑都不复杂,只需要根据场景调整细节即可满足大部分水印使用需求。