咱们从实现形式上来思考,既然要做水印,那必定要是全屏幕的,咱们会先想到几点
- 用一个 div 全屏fixed。
- 水印要和登录信息绑定,那么咱们从cookie中获取一下账号信息。
- 屏幕上一个大的水印,成果没有稀稀拉拉的小水印成果好。
- 水印之前的间距要小一点,这样能力减少覆盖面积。
- 要有避免篡改的性能。
列出以上这几点,咱们就顺次实现就好了。
第一点
很好实现。咱们create一个dom元素,插入到body中就能够了。
const divObj = document.createElement('div'); const styleStr = ` position:fixed; top:0; left:0; bottom:0; right:0; z-index:999999; background-repeat:repeat; `; divObj.setAttribute('style', styleStr); document.body.appendChild(divObj);
第二点
水印的内容咱们从cookie中获取一下。
const user = /user_name=([^;]+)/.exec(document.cookie); const name = Array.isArray(user) && user.length === 2 && user[1] ? user[1] : '配置的水印';
这里的name就是咱们拿到的用户信息。
第三四点
这两个综合思考的话,将name作为一个背景图 而后repeat就能够了。咱们要将文字转为图片,那么canvas是一个不错的抉择。同时咱们要思考到这个图片不易过大(为了合乎第三点),所以咱们就依照200*100的尺寸吧。
const canvasObj = document.createElement('canvas'); const canvas2d = canvasObj.getContext('2d'); canvasObj.width = 200; canvasObj.height = 100; canvas2d.font = fontSize + 'px Arial'; canvas2d.fillStyle = 'rgba(128,128,128,.6)'; // 这里文字的色彩淡一点,不要影响整体的好看 canvas2d.translate(canvasObj.width / 4, canvasObj.height / 2); canvas2d.rotate((-30 / 180) * Math.PI); canvas2d.fillText(name, 0, canvasObj.height / 2); // 将canvas 转为 dataURL const base64Url = canvasObj.toDataURL('image/png');
当初咱们就拿到了一个base64的地址,能够用来作为咱们的图片。
第五点
要有防篡改的性能,具体要体现在咱们创立的水印不能轻易的让他人给删了,其次,水印内容也不能轻易的被改了,无论是水印内容或者是水印的色彩款式等。
水印内容这一块因为咱们应用的是cookie中的登录信息,如果有人更改了cookie中的值,会导致登录信息生效,在sso这一侧就会被强制跳转到登录页面,所以这个能够交由登录零碎来做。
咱们的dom不能轻易的被删除和更改款式,那么咱们就须要用到 MutationObserver 这个api了,他的作用就是监听DOM的变动,并触发一个回调,咱们只需在回调中从新执行水印的办法就能够防止这个问题。
if (MutationObserver) { let waterMarkOb = new MutationObserver(function () { const _globalWatermark = document.querySelector(`domId`); // 当款式或者水印元素dom节点有改变时会从新绘制 if ( (_globalWatermark && _globalWatermark.getAttribute('style') !== styleStr) || !_globalWatermark ) { waterMarkOb.disconnect(); waterMarkOb = null; setWaterMark(); } }); // 指定察看对象 waterMarkOb.observe(document.body, { attributes: true, subtree: true, childList: true, }); }
最初一点
注意事项中有一点,就是咱们的水印性能尽可能的不要影响到业务的应用。
所以这里咱们要思考两点:
- 咱们的水印dom在全屏幕的最上层,尽管层级在99999,然而也不要要影响到上面的元素操作。所以咱们须要减少
pointer-events:none
的属性,不影响鼠标的操作。 咱们要在前端代码不变动的状况下上线水印的性能,那咱们就要从服务器上动手了,比方在nginx中,咱们能够应用 sub_filter 模块来替换返回的文本。例如
subs_filter "(<\/body>)" "$1<script src=\"https://cdn.xxx.com/watermark.js\"></script>" irg;
到此咱们的水印性能就算实现了。
一贯的准则,「BB is nothing,show me the code」。
整体的代码如下:
!(function () { // 一个配置 const options = { id: 'globalWaterMark', fontSize: 10, color: 'rgba(128,128,128,.6)', rotate: '-30', userName: "其余的身份" }; /** * 创立水印图片url */ function createWaterMark() { const { fontSize, color, id } = options; const user = /user_name=([^;]+)/.exec(document.cookie); const name = Array.isArray(user) && user.length === 2 && user[1] ? user[1] : options.userName; const canvasObj = document.createElement('canvas'); const canvas2d = canvasObj.getContext('2d'); canvasObj.width = 200; canvasObj.height = 100; canvas2d.font = fontSize + 'px Arial'; canvas2d.fillStyle = color; canvas2d.translate(canvasObj.width / 4, canvasObj.height / 2); canvas2d.rotate((-30 / 180) * Math.PI); canvas2d.fillText(name, 0, canvasObj.height / 2); // 将canvas 转为 dataURL const base64Url = canvasObj.toDataURL('image/png'); return base64Url; } function setWaterMark() { const { fontSize, color, id } = options; const url = createWaterMark(); const target = document.getElementById(id); if(target){ document.body.removeChild(target) } const divObj = document.createElement('div'); divObj.id = options.id; const styleStr = ` position:fixed; top:0; left:0; bottom:0; right:0; z-index:999999; background-repeat:repeat; pointer-events:none; background-image:url('${url}')`; divObj.setAttribute('style', styleStr); document.body.appendChild(divObj); // 监听DOM变动 const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; if (MutationObserver) { let waterMarkOb = new MutationObserver(function () { const _globalWatermark = document.querySelector(`#${id}`); // 当款式或者水印元素dom节点有改变时会从新绘制 if ( (_globalWatermark && _globalWatermark.getAttribute('style') !== styleStr) || !_globalWatermark ) { waterMarkOb.disconnect(); waterMarkOb = null; setWaterMark(); } }); // 指定察看对象 waterMarkOb.observe(document.body, { attributes: true, subtree: true, childList: true, }); } } document.addEventListener('DOMContentLoaded', function () { setWaterMark(); });})();