为防止信息泄露,给网页加水印是一种常见的方法;本篇文章介绍一种添加水印的方法,经测试效果不错;特点:
-
不影响现有代码
-
可以任意给网页的不同部分添加水印
-
纯前端js实现
-
可简单防止用户通过浏览器开发者工具隐藏水印
实现
代码地址:https://github.com/leozcx/watermark.git
最终效果如下:
生成水印
1. 生成图片
水印的特点是,包含一段标识信息,同时需要覆盖足够的区域,这很自然想到用background,指定image,并让它在x,y 2个方向上重复展示;那么问题是:如何生成image呢?
简单调研,前端生成image主要用到canvas;canvas标签是HTML5新增的元素,主要作用是支持用js画图;canvas是个很强大的标签,有关canvas的详细介绍可以移步[这里] (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). 用canvas把信息画成图之后,调用toDataURL()
方法就可以得到一个url,该url实际包含了Base64过的图像信息,可以直接用在background上。
代码比较简单,演示如下:
function createImageUrl(options) {
var canvas = document.createElement('canvas');
var text = options.text;
canvas.width = options.width
canvas.height = options.height
var ctx = canvas.getContext('2d');
ctx.shadowOffsetX = 2; //X轴阴影距离,负值表示往上,正值表示往下
ctx.shadowOffsetY = 2; //Y轴阴影距离,负值表示往左,正值表示往右
ctx.shadowBlur = 2; //阴影的模糊程度
// ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; //阴影颜色
ctx.font = options.font
ctx.fillStyle = "rgba(204,204,204,0.45)"
ctx.rotate(options.rotateDegree);
ctx.translate(options.trax, options.tray);
ctx.textAlign = 'left';
ctx.fillText(text, 35, 32); //实体文字
return canvas.toDataURL('image/png')
}
2. 设置背景
有了图片之后,只要把图片放在某个div的background上就行了;这里只需要注意几个要点:
- position: fixed; 这样可以保证不管内容如何滚动,水印都能显示;
- pointer-events: none; 阻止水印影响页面内容
- 动态计算出水印的位置
代码示例:
function createContainer(options, forceCreate) {
let oldDiv = document.getElementById(options.id)
if(!forceCreate && oldDiv)
return container
let url = createImageUrl(options)
var div = oldDiv ? oldDiv : document.createElement('div');
div.id = options.id
let parentEl = options.preventTamper ? document.body : (options.parentEl || document.body)
if(typeof parentEl === 'string') {
if(parentEl.startsWith('#'))
parentEl = parentEl.substring(1)
parentEl = document.getElementById(parentEl)
}
let rect = parentEl.getBoundingClientRect()
options.style.left = (options.left || rect.left) + 'px'
options.style.top = (options.top ||rect.top) + 'px'
div.style.cssText = getStyleText(options)
div.setAttribute('class', '')
backgroundUrl = 'url(' + url + ') repeat top left';
div.style.background = backgroundUrl
!oldDiv && parentEl.appendChild(div)
return div
}
防止客户端篡改
好了,现在水印可以显示了;但这样只能防止小白用户,稍微有点技术的用户就知道,可以用浏览器的开发者工具来动态更改dom,比如display: none;就可以隐藏水印;所以还需要加一点机制防止用户进行篡改;当然,从本质上来说是没有绝对的办法在客户端去防用户的,所以这里的trick只是增加了用户篡改的难度。
一种思路是监测水印div的变化,一旦发生变化,则重新生成水印;沿着这个思路往下走,如何监测水印div变化呢?
1. 不间断比较div的值
开始采用的是这种方法,记录刚生成的div的innerHTML,每隔几秒就取一次新的值,通过比较2者的md5,如果发生变化则重新生成。但这个方法有几个缺点:
- 滞后性,修改不能马上被监测后;而如果间隔时间过短,则可能影响性能;
- 生成md5也有不小的开销,特别是打开多个页面的时候;
所以这种方法不可行。
2. MutationObserver
通过查询文档,发现浏览器提供了一种监测元素变化的API: (MutationObserver) [https://developer.mozilla.org/en/docs/Web/API/MutationObserver], 并且主流浏览器都支持,所以改用这种方式监测;代码示例:
function observe(options, observeBody) {
let target = container
observer = new MutationObserver(function(mutations) {
observer.disconnect()
container = createContainer(options, true)
var config = { attributes: true, childList: true, characterData: true, subtree:true };
observer.observe(target, config);
});
var config = { attributes: true, childList: true, characterData: true, subtree:true };
observer.observe(target, config);
//observe body element, recreate if the element is deleted
var pObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if(m.type === 'childList' && m.removedNodes.length > 0) {
let watermarkNodeRemoved = false
for(let n of m.removedNodes) {
if(n.id === options.id) {
watermarkNodeRemoved = true
}
}
container = createContainer(options)
observe(options, false)
}
})
});
pObserver.observe(document.body, {childList: true,subtree:true});
}
这里有一点需要注意,MutationObserver只能监测到诸如属性改变、增删子结点等,对于自己本身被删除,是没有办法的;这个可以通过一个小trick来解决:同时监测父结点,看div是否被删除。