一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
今天是假期的前一天,我猜各位只想要快点关电脑下班回家吧。
今天我们就来封装一个水印组件吧。很快的,相信我
1. 生成普通水印
在 useEffect 中,通过 createWaterDom 函数进行水印组件的绘制。使用 createElement 创建一个 div。
import { useEffect } from "react";
const WaterMark = ({ text = "这里是水印" }) => {
const _text = text;
useEffect(() => {
createWaterDom(document.querySelector('#App'));
}, []);
const createWaterDom = (element) => {
let dom = document.createElement("div");
};
};
export default WaterMark;
为了防止类名重复,需要使用随机函数生成类名。
// 动态生产 classname
const nameGenerator = () => {
let result = "";
let length = 2 + Math.ceil(Math.random() * 7);
let dict = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"g",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
for (let i = 0; i < length; i++) {
result += dict[Math.ceil(Math.random() * 26 - 1)] || 'a';
}
return result;
};
const elementAttributeName = nameGenerator();
createWaterDom 函数会传入一个节点,用于挂载水印元素。handleAddWaterMark 函数生成水印元素。
const createWaterDom = (element) => {
let dom = document.createElement("div");
dom.className = elementAttributeName;
element.appendChild(dom);
handleAddWaterMark(
_text,
document.querySelector(`.${elementAttributeName}`)
);
};
handleAddWaterMark 函数,使用 canvas 生成水印,并使用 toDataURL 将其转化为 base64 格式的图片。生成之后删除多余的 canvas 元素。
这里面的参数其实可以抽离处理,向外暴露给使用者的。不过,这就要看个人情况了,想要抽离出来也行。
const handleAddWaterMark = (str, element) => {
let rotate = -25;
let fontWeight = "normal";
let fontSize = "14px";
let fontFamily = "SimHei";
let fontColor = "rgba(0, 0, 0, 0.05)";
let rect = {
width: 370,
height: 300,
left: 10,
top: 150,
};
let can = document.createElement("canvas");
can.className = "mark-canvas";
let watermarkDiv = element;
watermarkDiv.appendChild(can);
can.width = rect.width;
can.height = rect.height;
can.style.display = "none";
can.style.zIndex = "999";
let cans = can.getContext("2d");
cans.rotate((rotate * Math.PI) / 180);
cans.font = `${fontWeight} ${fontSize} ${fontFamily}`;
cans.fillStyle = fontColor;
cans.textAlign = "center";
cans.textBaseline = "middle";
cans.fillText(str, rect.left, rect.top);
// 使用 canvas 生成图片
const styleStr = `height: inherit !important; background-color: transparent !important; transform: inherit !important; visibility: visible !important; display: block !important; position: absolute !important; z-index: 99 !important; opacity: 1 !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; pointer-events: none !important; background-repeat: repeat !important; background-image: url(${can.toDataURL(
"image/png"
)}) !important;`;
watermarkDiv.setAttribute("style", styleStr);
// 生成之后删除多余的 canvas 元素
let canvasDom = document.querySelector(".mark-canvas");
if (canvasDom) {
canvasDom.parentElement.removeChild(canvasDom);
}
};
到这一个水印组件就完成了,让我们来看看效果
是不是很 nice~
但是,少年,你以为这就结束了?
你以为使用这个的人不知道 F12,delete html 吗?
所以我们要干嘛呀~~
监听删除。一删除,我们就马上生成,看谁赢!
2. 监听删除水印节点
MutationObserver接口提供了监视对DOM树所做更改的能力。
MutationObserver 可以观察整个 文档、DOM 树的一部分 或 具体 dom 元素,主要是观察元素的 属性、子节点、文本 的变化,并且可以在 DOM 被修改时异步执行回调。
MutationObserver 接口是为了取代废弃的 MutationEvent:
- DOM Level 2 规范中描述的 MutationEvent 定义了一组会在各种 DOM 变化时触发的事件。由于浏览器事件的实现机制,这个接口出现了严重的性能问题。因此,DOM Level 3 规定废弃了这些事件。
- MutationObserver 接口更实用、性能更好
import { useEffect } from "react";
const WaterMark = ({ text = "这里是水印" }) => {
const _text = text;
const containObserver = () => {
let bodyObserver = new MutationObserver((mutationsList) => {
// 监听到 dom 节点被删除
return mutationsList.forEach((mutation) => {
if (mutation.removedNodes.length > 0) {
mutation.removedNodes.forEach((_target) => {
// 删除的节点是水印
if (_target.className === elementAttributeName) {
createWaterDom(document.querySelector('#App'))
}
})
}
})
});
bodyObserver.observe(document.querySelector('#App'), {
childList: true
})
}
useEffect(() => {
createWaterDom(document.querySelector('#App'));
containObserver();
});
// ....
};
export default WaterMark;
你以为这就万无一失了吗?
还是刚刚那句话,打开 F12,修改这个 className,也是可以删除滴,啦啦啦啦啦。所以需要监听随机生成的类名。
import { useEffect } from "react";
const WaterMark = ({ text = "这里是水印" }) => {
//....
const createWaterDom = (element) => {
// ....
// 监听随机生成的类名
let observer = new MutationObserver((mutationsList) => {
mutationsList.forEach((item) => {
if (item.type === "attributes") {
dom.parentElement.removeChild(dom);
createWaterDom(document.querySelector('#App'))
}
})
});
observer.observe(dom, {
attributes: true,
childList: true
})
};
};
export default WaterMark;
抽离参数,由外界传入,加一点点性能优化,就大功告成啦~~~~
import { useEffect, useCallback } from "react";
const WaterMark = ({ text = "", mountElement = "" }) => {
const _text = text;
let containMutationObserver = null;
const containObserver = useCallback(() => {
let bodyObserver = new MutationObserver((mutationsList) => {
// 监听到 dom 节点被删除
return mutationsList.forEach((mutation) => {
if (mutation.removedNodes.length > 0) {
mutation.removedNodes.forEach((_target) => {
// 删除的节点是水印
if (_target.className === elementAttributeName) {
createWaterDom(document.querySelector(`#${mountElement}`))
}
})
}
})
});
containMutationObserver = bodyObserver;
bodyObserver.observe(document.querySelector(`#${mountElement}`), {
childList: true
})
}, [])
useEffect(() => {
createWaterDom(document.querySelector(`#${mountElement}`));
containObserver();
}, []);
// 动态生产 classname
const nameGenerator = () => {
let result = "";
let length = 2 + Math.ceil(Math.random() * 7);
let dict = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"g",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
for (let i = 0; i < length; i++) {
result += dict[Math.ceil(Math.random() * 26 - 1)] || 'a';
}
return result;
};
const elementAttributeName = nameGenerator();
// 使用 canvas 创建水印,并进行添加
const handleAddWaterMark = (str, element) => {
let rotate = -25;
let fontWeight = 'normal';
let fontSize = '14px';
let fontFamily = 'SimHei';
let fontColor = 'rgba(0, 0, 0, 0.05)';
let rect = {
width: 370,
height: 300,
left: 110,
top: 150
}
let can = document.createElement('canvas');
can.className = 'mark-canvas';
let watermarkDiv = element;
watermarkDiv.appendChild(can);
can.width = rect.width;
can.height = rect.height;
can.style.display = 'none';
can.style.zIndex = '999';
let cans = can.getContext('2d');
cans.rotate((rotate * Math.PI) / 180);
cans.font = `${fontWeight} ${fontSize} ${fontFamily}`;
cans.fillStyle = fontColor;
cans.textAlign = 'center';
cans.textBaseline = 'middle';
// 先放弃检查字体的宽度
cans.fillText(str, rect.left, rect.top);
// 使用 canvas 生成图片
const styleStr = `height: inherit !important; background-color: transparent !important; transform: inherit !important; visibility: visible !important; display: block !important; position: absolute !important; z-index: 99 !important; opacity: 1 !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; pointer-events: none !important; background-repeat: repeat !important; background-image: url(${can.toDataURL(
'image/png'
)}) !important;`;
watermarkDiv.setAttribute('style', styleStr);
// 生成之后删除多余的 canvas 元素
let canvasDom = document.querySelector('.mark-canvas');
if (canvasDom) {
canvasDom.parentElement.removeChild(canvasDom);
}
}
const createWaterDom = (element) => {
let dom = document.createElement("div");
// 生成随机动态类名
dom.className = elementAttributeName;
// 向你需要的元素中添加水印
element.appendChild(dom);
handleAddWaterMark(
_text,
document.querySelector(`.${elementAttributeName}`)
);
// 监听随机生成的类名
let observer = new MutationObserver((mutationsList) => {
mutationsList.forEach((item) => {
if (item.type === "attributes") {
containMutationObserver.disconnect();
dom.parentElement.removeChild(dom);
createWaterDom(document.querySelector(`#${mountElement}`))
containMutationObserver.observe(document.querySelector(`#${mountElement}`), {
childList: true
})
}
})
});
observer.observe(dom, {
attributes: true,
childList: true
})
};
};
export default WaterMark;
如何使用:
<div className="App" id="App">
<WaterMark
text="这里是一个水印这里是一个水印这里是一个水印"
mountElement="App"
/>
</div>