前言
在Web项目中,为了保护知识产权,或者后台系统快速定位到个人,页面水印的存在是非常有必要的。下面分享一种简单的前端添加页面水印的方式。
技术分析
从效果来说,希望实现的效果是在需要加载水印的盒子上覆盖上水印信息。首先想到的方案就是添加一个新的盒子,填充满想要添加水印的元素。然后将水印设置为背景图。不过后来想到,如果一个页面上很多元素想要分别添加水印,这样需要的操作太繁琐,而且会插入很多不必要的dom。所以改为使用:after
伪元素的方式来实现。定义好伪元素之后,只需要在想要添加水印的dom上增加一个类名就可以了。不过两种方案首先需要实现的就是通过文字生成图片。
实现方案
1. 生成背景图
实现自定义文字内容的图片,可以通过canvas
来生成一个自定义文字的画布,然后调用canvas.toDataURL()
生成base64字符串,作为元素的背景图;实现代码如下
// 配置类型定义
type imgOptions = {
// 宽度
width: number;
// 高度
height: number;
// 水印内容
content: string;
// 水印字体
font: string;
// 水印颜色
color: string;
// 偏转角度(deg)
rotateDegree: number;
// X轴偏移量
x: number;
// Y轴偏移量
y: number;
};
function createImgBase(options: imgOptions): string {
const canvas = document.createElement('canvas');
const text = options.content;
// 因为要实现文字交错效果,所以这里生成两倍宽度的图片
canvas.width = options.width * 2;
canvas.height = options.height;
const ctx = canvas.getContext('2d');
if (ctx) {
// X轴阴影距离,负值表示往上,正值表示往下
ctx.shadowOffsetX = 2;
// Y轴阴影距离,负值表示往左,正值表示往右
ctx.shadowOffsetY = 2;
// 阴影的模糊程度
ctx.shadowBlur = 2;
ctx.font = options.font;
ctx.fillStyle = options.color;
ctx.rotate(options.rotateDegree);
ctx.textAlign = 'left';
ctx.fillText(text, options.x, options.y);
}
return canvas.toDataURL('image/png');
}
复制代码
2. 将背景图添加到伪元素内
根据技术分析方案,可以将生成的img设为某个类::after
伪元素的背景图,这样只需要给想要添加水印的dom添加上自定义的类名,就可以给此元素添加水印了,实现方法如下
// 配置类型
type watermarkOptions = {
// 自定义类名
className?: string;
// 宽度
width?: number;
// 高度
height?: number;
// 文案内容
content?: string;
// 字体
font?: string;
// 颜色
color?: string;
// 偏转角度
rotate?: number;
// 定位方式
position?: string;
// 顶部距离
top?: number;
// 左侧距离
left?: number;
// 层级
zIndex?: number;
};
/**
* 生成水印
* @param {String} className 水印类名
* @param {Number} width 宽度
* @param {Number} height 高度
* @param {String} content 内容
* @param {String} font 字体
* @param {String} color 自定义样式: 如字体颜色(使用RGBA)
* @param {Number} rotate 翻转角度
* @param {String} position 水印定位方式
* @param {Number} top 距离顶部位置
* @param {Number} left 距离左部位置
* @param {Number} zIndex 水印层级
*/
function genWaterMark({
className = 'watermarked',
width = 340,
height = 240,
content = '水印',
font = '14px PingFang SC, sans-serif',
color = 'rgba(156, 162, 169, 0.3)',
rotate = -14,
position = 'absolute',
top = 0,
left = 0,
zIndex = 1000,
}: watermarkOptions): void {
const option = {
width,
height,
content,
font,
color,
rotateDegree: (rotate * Math.PI) / 180,
};
// 为了实现交错水印的效果,此处生成两张位置不同的水印 固定相对位置
const dataUri1 = createImgBase({
...option,
x: 100,
y: 140,
});
const dataUri2 = createImgBase({
...option,
x: 400,
y: 340,
});
let defaultStyle = document.createElement('style');
defaultStyle.innerHTML = `.${className}:after {
content: '';
display: block;
width: 100%;
height: 100%;
${top || top === 0 ? `top: ${top}px;` : ''}
${left || left === 0 ? `left: ${left}px;` : ''}
background-repeat: repeat;
pointer-events: none;
}`;
let styleEl = document.createElement('style');
styleEl.innerHTML = `.${className}:after
{
${position ? `position: ${position}` : ''};
${zIndex ? `z-index:${zIndex}` : ''};
background-image: url(${dataUri1}), url(${dataUri2});
background-size: ${option.width * 2}px ${option.height}px;
}`;
document.head.appendChild(defaultStyle);
document.head.appendChild(styleEl);
}
复制代码
3. 调用方法
- 新建一个HTML页面,引入js
<style>
body {
margin: 0;
}
* {
box-sizing: border-box;
}
.main {
height: 100vh;
width: 100vw;
padding: 15px;
display: flex;
justify-content: space-between;
}
.main .left,
.main .right {
position: relative;
height: 100%;
width: 49%;
border: 1px solid #66ccff;
}
</style>
<body>
<script src="./water-mark.js"></script>
<div class="main">
<div class="left"></div>
<div class="right"></div>
</div>
</body>
复制代码
效果如下
- 调用方法
const option = {
content: '测试水印',
className: 'watermarked',
};
genWaterMark(option);
复制代码
- 首先给
.left
添加类名试验效果
<div class="main">
<div class="left watermarked">
</div>
<div class="right"></div>
</div>
复制代码
- 再看下同时给
.right
添加水印的效果
<div class="main">
<div class="left watermarked">
</div>
<div class="right watermarked"></div>
</div>
复制代码
总结
至此,前端生成并添加水印的方案基本完成。不过此方案的安全性是很脆弱的,稍微懂点前端知识的人都知道可以通过控制台修改css来隐藏水印,这样就起不到保护效果了。因此,需要设计一个方案,来禁止用户通过控制台隐藏or删除水印。水印防删解决方案