上一节 (网站页面水印效果的实现(上):页面水印的添加)和大家介绍了页面水印如何生成以及如何使用,这一节来介绍如何防止水印被篡改,其实重点就是水印被删除。毕竟辛辛苦苦搞出来的水印,是为了保护自己的知识产权,结果被人轻轻松松就给搞掉了,岂不是悲哀。
要防止被篡改,就需要先知道有哪些方法可以进行篡改。直接在页面操作不太可能,但是其实如果不加防范,可以非常轻松的就把水印干掉。可以从客户端浏览器的开发者工具中,通过浏览Elements信息,找到水印对应的div将其删除,就可以轻松的将水印删除。
知道了水印如何被攻击和篡改,也就大致有了应对的解决方案。只要在程序设计时,监听水印信息的变化,从而判断是否需要重新生成水印即可。那又如何才能知道水印信息是否发生变化哪?
上一节中,曾提到有四点需要注意的关键因素:
一、要使用js动态加载div元素,不能通过直接在页面添加div标签的方式创建
现在大家明白是为什么了吧,如果直接在html创建div元素,一旦被删除,如何重新生成,反而相当麻烦,倒不如从一开始就把他搞成动态的,这就简单多了,一旦发现前端将水印签名遮罩层div给删除了,直接重新运行一遍被watchEffect包裹的代码逻辑即可恢复水印。
二、要使用watchEffect方法而不是onMounted处理页面元素逻辑
因为onMounted只会运行一次,除非页面被销毁重构,否则无论页面信息如何变化,都不会再执行内部的代码逻辑,因此也就无法恢复水印信息;
三、要使用js动态加载css样式,不能通过class方式添加
因为前端去修改你的class后台是监控不到的,他可以通过修改class来破坏水印信息。
这就是为什么,在上一节中将这几点给大家重点标红提示出来,当时没有做过多解释,在这里揭晓答案。
接下来处理如何监听的问题,JS为我们提供了一个接口-MutationObserver,用于观察DOM树的变化,可以监视指定DOM元素的所有变化,包括元素本身及其各级子元素的属性、内容的变化,并在变化发生时执行相应的操作。在不需要轮询的情况下,实时监测DOM变化,以便做出相应的处理。
于是在onMounted创建MutationObserver对象,并设置回调函数和监听配置。
onMounted(() => {
obs = new MutationObserver((res) => {
// console.info(res);
for (let r of res) {
for (let dom of r.removedNodes) {
// 删除水印遮罩层div元素,触发重新生成水印
if (customDiv == dom) {
flag.value++;
return;
}
}
// 修改水印遮罩层div属性,触发重新生成水印
if(customDiv == r.target){
flag.value++;
return;
}
}
})
obs.observe(yaken.value, {
childList: true, //监听目标节点的子节点的增加或删除事件
attributes: true, //监听目标节点的属性变化
subtree: true, //监听目标节点的所有后代节点而不仅仅是直接子节点
characterData: true //监听目标节点内容的变化
});
})
首先创建了一个MutationObserver对象,并设置了回调函数,一旦监听到了变化,就将触发回调函数里面的逻辑。回调函数返回的结果res其实就是一个消息记录的集合,通过遍历这个集合,找到对应操作触发的消息,在通过比对目标对象与触发监听消息的对象是否一致,来判断某个页面组件及其各层子组件的属性、内容等是否被操作过,从而决定要进行什么样的后处理。在这段代码中,主要监听了两个事件,一个是节点删除事件的消息,另一个是目标节点属性被修改事件消息。只要这两项信息被修改,就立即触发watchEffect内部逻辑,重构水印信息(网站页面水印效果的实现(上):页面水印的添加)。哪又如何触发这个逻辑哪?要在watchEffect中监听整个div的变化吗?当然不是,watchEffect只需要知道情况发生变化了即可,它并不需要了解变化的具体细节,所以只需要设置一个标志位参数flag就够了。只要情况发生变化,就改变标志位的值,watchEffect通过监听标志位参数变化触发内部逻辑。与
watch相同的是,它
可以监听数据的变化,并在数据变化时自动执行传入的回调函数。与
watch不同的是
,它不需要指定要监听的具体数据,它会自动追踪回调函数中所使用的响应式数据,并在数据发生变化时触发回调。所以标志位参数flag一定要设置成响应式的,然后可以直接放到回到函数中。
创建了MutationObserver对象并设置好了回调函数就完事了吗?当然没有,上面乱七八糟的一大堆,都是对监听的后处理操作,你到底要监听谁、监听那些方面的信息你还没有配置,所以还需要给MutationObserver对象配置要监听的目标对象是谁,并且告诉它你要监听那些方面的变化。这里监听的对象是yaken引用的父元素的div,而不是我们动态创建的div,因为如果你监控了动态创建的div,一旦前端把它删除了,监控对象就消失了,监控机制也就失效了。这样一来,还得反复给重建构建的div元素配置监听,这样做显然不太明智。因此直接监控其父元素的变化。至于要监控哪些方面的变化,这里配置了四个属性:节点增删事件、节点属性变化、节点内容变化、各级子节点变化。
监听配置好了,再来修改一下watchEffect的代码逻辑:
let flag = ref(0);
watchEffect(() => {
flag.value;
if (!yaken.value) {
return;
} else {
const {base64, adaptiveSize} = customWaterMark.value;
// 判断元素是否为空,若不为空先移除元素
if (customDiv) {
customDiv.remove()
}
customDiv = document.createElement('div');
customDiv.style.backgroundImage = `url(${base64})`;
customDiv.style.backgroundSize = `${adaptiveSize}px ${adaptiveSize}px`;
customDiv.style.backgroundRepeat = 'repeat';
// customDiv.style.width = '100%';
// customDiv.style.height = '100%';
customDiv.style.zIndex = '999';
// 设置绝对定位,以便图片能够填充整个区域
customDiv.style.position = 'absolute';
// 子元素相对于父元素的内边距,设置为0代表子元素完全覆盖父元素
customDiv.style.inset = '0';
// 必须设置此属性,否则水印遮罩层会阻止下层页面的操作事件
customDiv.style.pointerEvents = 'none';
yaken.value.append(customDiv);
}
})
和上一节 (网站页面水印效果的实现(上):页面水印的添加)代码比较一下,只是多了一个监听对象flag,以及添加了一个判断,用来判断customDiv对象是否为空,不为空,说明存在页面元素,那就先移除它再重新构建一个div即可。
最后不要忘记,在组件卸载的时候要取消监听,并且要将创建的水印区域div对象置空,以免存在内存泄漏的风险。
onUnmounted(() => {
obs.disconnect();
customDiv = null;
})
到此为止,一个简单的水印防篡改的代码就完成了。当然,除了页面元素的删除、属性内容的修改触发了水印信息重构外,还可以根据实际情况自定义更多的触发条件,以保证水印安全,这里就不做过多介绍了,大家在实际工作中用到了再自行添加即可。接下来可以做个测试,此时再打开浏览器控制台,删除水印div试试看,你会发现根本删不掉。其实不是没删掉,是删掉了,只不过瞬间又重新生成了一个新的而已。