目录
一、应用场景
二、实现原理
三、详细开发
1.水印的实现方式
2.防止用户通过控制台修改样式去除水印效果(可跳过,有弊端)
3.水印的使用
(1)单页面/全局使用
(2)全局使用+个别页面去掉
四、总结
一、应用场景
在网页中添加水印的作用可以有多个方面,一个重要的作用就是版权保护,防止未经授权就复制截图或使用,在文档中可以帮助标识文档的来源,审查追踪等,也可以展示企业信息,或者作为提示信息告诉用户当前页面谨慎处理,也能在敏感信息的页面提示用户保护信息安全等。注意:制作页面中的水印要平衡用户体验和需求,确保水印不要太大,太密,太突兀,干扰页面浏览和操作。
二、实现原理
在页面里添加水印,一种是特定页面加水印,那么本页面加水印功能即可,用CSS/JavaScript都可以实现,另一种是全局都加水印,这种可以考虑某些页面不需要加水印,在路由守卫或者其他地方去掉即可。
写在前面,水印的实现原理:创建一个canvas,画一个客户端高x客户端宽的画布,里面画满水印,并将其的层级z-index设置最高,使其一直显示在界面的最上方。
水印的内容,可以根据应用的场景而变换,比如版权保护,这些最好显示版权的归属方之类的,有些出于标识来源加的水印,则需要考虑当前用户的信息,比如用户名等等。
实现效果如下:
三、详细开发
首先谈一下水印的实现方式,再说怎么加水印。
1.水印的实现方式
我们可以在utils下新建一个文件:watermark.js,代码如下:
let watermark = {}
let idd = "1.23452384164.123412416"
let setWatermark = (str, srt1, srt2, srt3) => {let id = iddif (document.getElementById(id) !== null) {document.body.removeChild(document.getElementById(id))}//创建一个画布let can = document.createElement("canvas")//设置画布的长宽can.width = 600can.height = 450 let cans = can.getContext("2d")//旋转角度cans.rotate((-15 * Math.PI) / 180)cans.font = "18px Vedana"//设置填充绘画的颜色、渐变或者模式cans.fillStyle = "rgba(200, 200, 200, 0.40)"//设置文本内容的当前对齐方式cans.textAlign = "left"//设置在绘制文本时使用的当前文本基线cans.textBaseline = "Middle"//在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)cans.fillText(str + srt1, can.width / 8, can.height / 2)cans.fillText(srt2, can.width / 8, can.height / 2.3)cans.fillText(srt3, can.width / 8, can.height / 2.7)let div = document.createElement("div")div.id = idconst styleStr = `position:fixed;visibility:visible !important;top:30px;width:${document.documentElement.clientWidth}px;height:${document.documentElement.clientHeight}px;left:0;z-index:100000;pointer-events:none;background:url('${can.toDataURL("image/png")}') left top repeat`div.setAttribute("style", styleStr)// div.style.width = document.documentElement.clientWidth + 'px';// div.style.height = document.documentElement.clientHeight + 'px';document.body.appendChild(div)//此方法是防止用户通过控制台修改样式去除水印效果/* MutationObserver 是一个可以监听DOM结构变化的接口。 */// const observer = new MutationObserver(() => {// const wmInstance = document.getElementById(id)// if (// (wmInstance && wmInstance.getAttribute("style") !== styleStr) ||// !wmInstance// ) {// //如果标签在,只修改了属性,重新赋值属性// // console.log("水印属性修改了")// if (wmInstance) {// // 避免一直触发// observer.disconnect();// console.log("水印属性修改了")// wmInstance.setAttribute("style", styleStr)// } else {// /* 此处根据用户登录状态,判断是否终止监听,避免用户退出后登录页面仍然有水印 */// if (store.state.user.token) {// //标签被移除,重新添加标签// // console.log('水印标签被移除了');// document.body.appendChild(div)// } else {// observer.disconnect()// }// }// }// })// observer.observe(document.body, {// attributes: true,// subtree: true,// childList: true,// })return id;
}// 该方法只允许调用一次
watermark.set = (str, srt1, srt2, srt3) => {let id = setWatermark(str, srt1, srt2, srt3)setInterval(() => {if (document.getElementById(id) === null) {id = setWatermark(str, srt1, srt2, srt3)}}, 500)window.onresize = () => {setWatermark(str, srt1, srt2, srt3)}
}
// 移除
const outWatermark = id => {if (document.getElementById(id) !== null) {const div = document.getElementById(id)div.style.display = "none"}
}watermark.remove = () => {const str = iddoutWatermark(str)
}// 将 watermark 的控制方法挂载到 window 对象上
window.watermark = watermarkexport default watermark
上面的代码,很多人都写过,这里实现的也是大致效果,原理简单来讲就是:创建一个canvas,画一个客户端高x客户端宽的画布,里面画满水印,并将其的层级z-index设置最高,使其一直显示在界面的最上方,水印的效果就根据业务需求来调整。
这里将水印的控制方法(set和remove)都挂载在了window上,那么不论在哪个页面使用都可以直接调window来操作水印,水印的传参设置了四个str,其实可以根据实际情况添加更多,定制各样的效果。
2.防止用户通过控制台修改样式去除水印效果(可跳过,有弊端)
这里就是指上面代码里注释的的功能,可根据需求添加。
这个功能的原理:
- 创建了一个
MutationObserver
实例,(MutationObserver 允许开发人员监视 DOM 树的变化,并在发生变化时执行相应的操作),通过传一个制定ID的元素,将其存储在wmInstance
变量中。- 然后检查
wmInstance
是否存在,及其style属性是否与指定的styleStr
变量相匹配,来判断水印是否需要更新。- 如果
wmInstance
存在且其style
属性不匹配,或者wmInstance
不存在,则进行相应的处理:
(1)如果wmInstance
存在,则更新其style
属性为styleStr
。
(2)如果wmInstance
不存在,则检查用户登录状态。如果用户已登录(假设通过store.state.user.token
判断),则向页面添加新的水印元素(假设该元素已在其他地方定义)。否则,断开observer
的监听。- 最后,调用
observer.observe()
方法开始观察文档主体的变化。选项对象指定了要观察的变化类型,包括attributes
、subtree
和childList
。
而MutationObserver是什么?
MutationObserver 是 Web API 中的一部分,用于监视 DOM(文档对象模型)树的变化。它允许开发人员异步地观察文档中的节点并对其进行相应的处理。
在 Web 开发中,DOM 是指用于表示文档结构的树形数据结构,它由节点(node)组成,每个节点代表文档中的不同部分,如元素、属性、文本等。DOM 的结构和内容可能在页面加载后发生变化,比如用户的交互行为、脚本操作等都可能导致 DOM 发生变化。
MutationObserver 提供了一种机制,让开发人员可以监视 DOM 树的这些变化,并在变化发生时执行回调函数。这使得开发人员可以更灵活地响应 DOM 变化,而不必通过定时器或事件监听器等方式来轮询检查 DOM。
使用 MutationObserver,开发人员可以监视以下类型的 DOM 变化:
- 属性的改变(例如,元素的属性值发生变化)。
- 节点的添加或删除(例如,元素被插入或从 DOM 中移除)。
- 子节点的改变(例如,元素的子节点被添加或移除)。
通过 MutationObserver,开发人员可以更有效地监视 DOM 变化,从而实现更灵活、高效的 DOM 操作和交互。这在诸如单页面应用(SPA)等需要动态更新页面内容的场景中特别有用。
(上述查自网络)
这个功能的弊端是, 如下图所示,如果浏览器修改窗口大小,也会触发水印的修改,并且水印的覆盖会带来一定视觉上的“卡顿”,实际使用中可能卡顿不是那么明显,但是这种情况也是值得考虑的。
3.水印的使用
使用的方式,有两种,局部使用和全局使用,就类似于我们引入UI组件库的组件一样,封装的水印js也需要局部或者全局注册。
(1)单页面/全局使用
这里就比较简单,我们在需要加水印的页面,引入水印,然后可以在mounted生命周期里调用它就行了。
import Watermark from "@/utils/watermark"mounted() {if (!Watermark) {Watermark = null// console.log("无水印",Watermark)return} else {Watermark.set('第一行','第二行','第三行','第二行')}},
如果是 全局使用,就在app.vue的页面里,根据当前页面的路径或其他标识来判断是否需要添加水印。
(2)全局使用+个别页面去掉
这个有多种实现方式,需要考虑业务场景,我这里推荐借助路由守卫,在router的路由守卫拦截的时候进行水印的set或者remove操作,如下:
router.afterEach((to) => {let Watermark= window.watermarkif(!Watermark ){Watermark=null// console.log(store.state.app.watermark,"store.state.app.watermark");return}if (to.fullPath === "/login" || to.fullPath === "/test") {Watermark.remove()} else {Watermark.set('第一行','第二行','第三行','第二行')}
})
使用路由守卫进行拦截的优点是:
路由守卫可以针对每个路由进行拦截,并判断是否需要添加水印,如果在特定路由不需要添加水印,可以在路由拦截时不调用水印脚本(或者remove),对水印添加的控制更加精细。
四、总结
在水印的实现里,第二种情况,我推荐结合两者的方法可以更好地满足不同场景下的需求,即在 app.vue中判断大部分页面是否需要添加水印,然后在路由守卫中针对个别页面进行额外的控制,这样页面就能满足大部分场景的要求。