一、水印的生成
明水印的生成方式主要可以归为两类,一种是 纯 html 元素(纯div),另一种则为背景图(canvas/svg)。
div实现
test = () => {
function cssHelper(el, prototype) {
for (const i in prototype) {
el.style[i] = prototype[i];
}
}
const waterWrapper = document.createElement('div');
cssHelper(waterWrapper, {
position: 'fixed',
top: '0px',
right: '0px ',
bottom: '0px',
left: '0px',
overflow: 'hidden',
display: 'flex',
'flex-wrap': 'wrap',
'pointer-events': 'none'
});
const waterHeight = 100;
const waterWidth = 180;
const { clientWidth, clientHeight } = document.documentElement || document.body;
const column = Math.ceil(clientWidth / waterWidth);
const rows = Math.ceil(clientHeight / waterHeight);
function createItem() {
const item = document.createElement('div');
item.innerHTML = 'test';
cssHelper(item, {
position: 'absolute',
top: '50px',
left: '50px',
fontSize: '16px',
color: '#000',
lineHeight: 1.5,
opacity: 0.1,
transform: 'rotate(-15deg)',
transformOrigin: '0 0',
userSelect: 'none',
whiteSpace: 'nowrap',
overflow: 'hidden'
});
return item;
}
for (let i = 0; i < column * rows; i += 1) {
const wrap = document.createElement('div');
cssHelper(wrap, Object.create({
position: 'relative',
width: `${waterWidth}px`,
height: `${waterHeight}px`,
flex: `0 0 ${waterWidth}px`,
overflow: 'hidden'
}));
wrap.appendChild(createItem());
waterWrapper.appendChild(wrap);
}
document.body.appendChild(waterWrapper);
}
背景图实现
canvas
.watermark {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
pointer-events: none;
background-repeat: repeat;
}
test = () => {
function createWaterMark() {
const angle = -20;
const txt = 'test';
const canvas = document.createElement('canvas');
canvas.width = 180;
canvas.height = 100;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 180, 100);
ctx.fillStyle = '#000';
ctx.globalAlpha = 0.1;
ctx.font = '16px serif';
ctx.rotate(Math.PI / 180 * angle);
ctx.fillText(txt, 0, 50);
return canvas.toDataURL();
}
const watermakr = document.createElement('div');
watermakr.className = 'watermark';
watermakr.style.backgroundImage = `url(${createWaterMark()})`;
document.body.appendChild(watermakr);
}
svg
.watermark {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
pointer-events: none;
background-repeat: repeat;
}
function createWaterMark() {
const svgStr =
`<svg xmlns="http://www.w3.org/2000/svg" width="180px" height="100px">
<text x="0px" y="30px" dy="16px"
text-anchor="start"
stroke="#000"
stroke-opacity="0.1"
fill="none"
transform="rotate(-20)"
font-weight="100"
font-size="16"
>
test
</text>
</svg>`;
return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
}
const watermakr = document.createElement('div');
watermakr.className = 'watermark';
watermakr.style.backgroundImage = `url(${createWaterMark()})`
document.body.appendChild(watermakr);
二、水印的防御
主要有三点
- 水印元素本身是否被移除
- 水印元素属性是否被篡改(display: none …)
- 水印元素的子元素是否被移除和篡改 (element生成的方式 )
MutationObserver
js 有一个方法叫做 MutationObserver,能够监控元素的改动。
.watermark {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
pointer-events: none;
background-repeat: repeat;
}
function createWaterMark() {
const angle = -20;
const txt = 'test';
const canvas = document.createElement('canvas');
canvas.width = 180;
canvas.height = 100;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 180, 100);
ctx.fillStyle = '#000';
ctx.globalAlpha = 0.1;
ctx.font = '16px serif';
ctx.rotate(Math.PI / 180 * angle);
ctx.fillText(txt, 0, 50);
return canvas.toDataURL();
}
const watermakr = document.createElement('div');
watermakr.className = 'watermark';
watermakr.style.backgroundImage = `url(${createWaterMark()})`;
document.body.appendChild(watermakr);
const targetNode = document.body;
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line func-names
const callback = function (mutationsList) {
// Use traditional 'for loops' for IE 11
for (const mutation of mutationsList) {
mutation.removedNodes.forEach((item) => {
if (item === watermakr) {
document.body.appendChild(watermakr);
}
});
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
}
三、水印的破解
- 打开了Chrome Devtools 找到对应的元素,直接按 delete 即可删除。
- 打开Chrome Devtools,点击设置 - Debugger - Disabled JavaScript 。
- 复制一个 body 元素,然后将原来 body 元素的删除。
- 打开一个代理工具,例如 charles,将生成水印相关的代码删除。
第一种方法只能用于没有任何防御的水印,如果用了防御手段,比如 MutationObserver
那么就要用后三种方法
参考链接
谈谈前端水印