一、需求背景
1.1 数据安全
出去安全考虑,浏览管理后台的时候必须加上网页水印,来防止用户截图或者录屏暴露敏感信息后无法追踪用户来源。
1.2 前后端技术方案优缺点对比
前端添加水印
安全系数较低,容易被懂前端技术的技术人员消除或者篡改
运算消耗本地浏览器的CPU算力
后端添加水印
安全系数较高,一般用在图片,视频水印上,可单独对源文件处理,难以消除
主要是后端机器生成水印,若碰到复杂情况,对服务器有一定压力
二、实现思路
2.1 技术原理
不影响现有代码是前提,我们可以创建一个和原来HTML BOX一样大小的水印节点,利用CSS属性的position:fixed或者absolute,让水印处于最顶层。
2.2 技术效果
2.3 技术方案
- 纯html(创建文字需要太多节点,影响页面渲染,抛弃)
- html + canvas(生成一张IMG用来当做background,性能相比第一种方案好)
- html + svg(生成一张IMG用来当做background,性能相比第一种方案好,但兼容性比canvas的差一些,可看下面2个图的对比)
三、代码实现
3.1 画图
// 创建一个画布
var can = document.createElement("canvas");
// 设置画布的长宽
can.width = options["width"];
can.height = options["height"];
var cans = can.getContext("2d");
// 旋转角度
cans.rotate((-20 * Math.PI) / 180);
cans.font = "100 " + options["fontSize"] + " 微软雅黑";
// 设置填充绘画的颜色、渐变或者模式
cans.fillStyle = options["color"];
// 设置文本内容的当前对齐方式
cans.textAlign = "left";
// 设置在绘制文本时使用的当前文本基线
cans.textBaseline = "Middle";
// 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
cans.fillText(content, can.width / 8, can.height / 2);
3.2 生成IMG
canvas的关键API toDataURL
var div = document.createElement("div");
// 水印ID命名规范
div.id = watermarkId;
div.style.pointerEvents = "none";
div.style.top = (0) + "px";
div.style.left = (0) + "px";
div.style.position = _options.boxId ? "absolute" : "fixed";
div.style.zIndex = "9999999";
div.style.width = ( _options.boxId ? _options.boxWidth : document.documentElement.clientWidth) + "px";
div.style.height = (_options.boxId ? _options.boxHeight : document.documentElement.clientHeight) + "px";
div.style.background = "url(" + can.toDataURL("image/png") + ") left top repeat";
// 将水印插入盒子中
_options.boxId ? document.getElementById(_options.boxId).appendChild(div) : document.body.appendChild(div);
3.3 容错
- 清除已存在的水印
var watermarkId = _options.boxId + '-watermark'
// 如果已经有相同命名的水印,清除掉
if (document.getElementById(watermarkId) !== null) {
_options.boxId ?
document.getElementById(_options.boxId).removeChild(document.getElementById(watermarkId))
:
document.body.removeChild(document.getElementById(watermarkId))
;
}
- 定时监控水印 采用setInterval轮询页面上,水印节点是否消失,如果消失了,重新创建水印
var watermarkId = setWatermark(content, options, _options);
setInterval(() => {
if (document.getElementById(watermarkId) === null) {
watermarkId = setWatermark(content, options, _options);
}
}, 500);
- 页面大小改变的时候,水印重新渲染
window.onresize = () => {
setWatermark(content, options, _options);
};
四、Vue组件封装和发版
4.1 组件封装
本质上是slot插槽的使用,只有简单的几行,核心代码在上面【代码实现】
<template lang="html">
<div :id="id" ref="watermask" style="position: relative;width:100%;height:100%">
<slot></slot>
</div>
</template>
<script>
import './shuiyin.js' // 核心代码
export default {
name: "WaterMask",
props: ['content','options','id'],
data() {
return {
watermarkId: '',
_options: {}
}
},
mounted() {
const {width,height} = this.$refs.watermask.getBoundingClientRect()
this._options = {
boxWidth: width,
boxHeight: height,
boxId: this.id
}
window["WaterMask"].set(this.content,this.options,this._options)
}
};
</script>
4.2 npm发版
发版就简单npm publish发到私库上,使用的时候,可直接在main.js引入组件后直接使用
- 全局注入组件
import watermask from '@watermask'
Vue.component(watermask.name, watermask)
- 组件使用
<template>
<water-mask :content="content1" :options="options1" :id="id1">
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
<p>VUE通过refs获取元素宽高</p>
</water-mask>
</template>
<script>
export default {
data () {
return {
content1: "水印内容1", options1: {}, id1: 'xxx',
}
}
}
</script>
五、参考文档
阿里ProComponents procomponents.ant.design/components/…