在项目中,我们可能会遇到添加水印效果的需求,下面将会介绍一种基本的实现方法。
一、代码实现
watermark.vue 文件
// watermark 样式
let style = `
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;
opacity:0.5;
`;
const getDataUrl = (binding: any) => {
const rotate = -20;
const canvas = document.createElement("canvas");
// canvas.style.width = "500";
// canvas.style.height = "500";
const ctx = canvas.getContext("2d");
ctx?.rotate((rotate * Math.PI) / 180);
ctx.font = binding.font;
ctx.fillStyle = binding.fillStyle;
ctx?.fillText(binding.text || "机密文件", -10, 108);
return canvas.toDataURL("image/png");
};
const WaterMarker = {
mounted(el: HTMLElement, binding: any) {
init(el, binding);
},
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
// 设置水印
setWaterMark(el, binding.value);
// 监控
createObserver(el, binding.value);
};
// 设置水印
const setWaterMark = (el: HTMLElement, binding: any = {}) => {
const url = getDataUrl(binding);
// 创建 waterMark
const waterMark = document.createElement("div");
waterMark.className = `water-mark`;
style = `${style}background-image: url(${url});`;
waterMark.setAttribute("style", style);
el.setAttribute("style", "position: relative;");
el.appendChild(waterMark);
};
const createObserver = (el: HTMLElement, binding: any) => {
const waterMarkEl = el.querySelector(".water-mark");
// Firefox和Chrome早期版本中带有前缀
const MutationObserver =
window.MutationObserver ||
window?.WebKitMutationObserver ||
window?.MozMutationObserver;
// 监听节点变化
const observer = new MutationObserver((mutationsList) => {
console.log("mutationsList", mutationsList);
if (mutationsList.length) {
const { removedNodes, type, target } = mutationsList[0];
const currStyle = waterMarkEl?.getAttribute("style");
if (removedNodes[0] === waterMarkEl) {
// 停止观察
observer.disconnect();
init(el, binding);
} else if (
type === "attributes" &&
target === waterMarkEl &&
currStyle !== style
) {
waterMarkEl.setAttribute("style", style);
}
}
});
observer.observe(el, {
childList: true,
attributes: true,
subtree: true,
});
};
export default WaterMarker;
在Directive的index.ts文件中引入水印(WaterMarker.vue)
import WaterMarker from "./WaterMarker";
interface IDirectives {
[key: string]: any;
}
// 自定义指令
const directives: IDirectives = {
waterMarker: WaterMarker,
};
export default {
install(app: any) {
Object.keys(directives).forEach((key) => {
app.directive(key, directives[key]);
});
},
};
引入
实现效果
二、实现思路
2.1 水印全局自定义指令
在这里我们采用的是全局自定义指令的方式,这样方面我们在想要的页面直接加上相应的指令即可。
2.2 canvas实现逻辑
水印相当于是一张重复的背景图片印上我们想要加入的名字等,所以我们很容易想到利用canvas进行实现,在上面getDataUrl 方法则是其实现逻辑。
2.3 利用MutationObserver 监控
MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力在这里,我们需要监听子节点,属性相应的变化。若改变了则重新渲染水印,从而防止用户行为而导致水印消失。
配置观察者对象行为的对象,该对象可以拥有下面这些属性:
属性 | 描述 |
---|---|
childList | 如果需要观察目标节点的子节点(新增了某个子节点,或者移除了某个子节点),则设置为true. |
attributes | 如果需要观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化),则设置为true. |
subtree | 除了目标节点,如果还需要观察目标节点的所有后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化),则设置为true. |
MutationRecord对象会作为第一个参数传递给观察者对象包含的回调函数,该对象有下面这些属性
属性 | 类型 | 描述 |
---|---|---|
type | String | 如果是属性发生变化,则返回attributes.如果是一个CharacterData节点发生变化,则返回characterData,如果是目标节点的某个子节点发生了变化,则返回childList. |
target | Node | 返回此次变化影响到的节点,具体返回那种节点类型是根据type值的不同而不同的. 如果type为attributes,则返回发生变化的属性节点所在的元素节点,如果type值为characterData,则返回发生变化的这个characterData节点.如果type为childList,则返回发生变化的子节点的父节点. |
removedNodes | NodeList | 返回被删除的节点,或者为null. |
以上只是列举了本次水印用到的一些属性,具体属性请参考文档
http://web.h3399.cn/MutationObserver.htm
三、弊端
上面水印效果能满足大部分要求,但如果客户懂点浏览器操作,水印效果也容易轻松去除
如上图,将Disable JavaSccript
勾选,页面将不能执行JavaSccript代码,从而水印也会消失
若哪位大佬有更好的解决方案,麻烦点赞评论或私聊告诉一声,感谢啦