经常有一些公司和组织出于系统文件或信息安全保密的需要,需要在系统网页上增加带有个人标识(系统账号或个人信息)的水印,可以简单防止截图外传
首先我们来看这样一个水印功能的实现思路,通常是在我们原有的网页上附上一个 DIV 层,将它设置绝对定位铺满整个窗口,然后 z-index 值尽量往大了设,保证让水印层处于当前网页所有元素的上面,又不影响当前网页的操作。
水印上的字体有两种方式添加:
- 第一种直接将字体用块元素包裹,动态设置绝对定位,然后通过 transform 属性旋转;
- 第二种通过在 canvas 上绘制出字体,设置好样式,然后以图片的样式导出,最后用图片作为水印层的背景图。
处于性能方面考虑,第二种方式最优。我们来看具体怎么实现?
作为一块独立的功能,我们在 Vue3 中常用 hooks 来实现,通过分析我们概括出实现水印需要的几个功能函数和对外接口:
对外接口
- 清除水印(clear)
- 设置水印(setWatermark)
核心功能函数
- 绘制文字背景图(createBase64)
- 绘制水印层(createWatermark)
- 页面随窗口大小调整更新(updateWatermark)
export function useWatermark(
appendEl: Ref = ref(document.body) as Ref
) {
// 绘制文字背景图
function createBase64() {}
// 绘制水印层
const createWatermark = () => {};
// 页面随窗口调整更新水印
function updateWatermark(){}
// 对外提供的设置水印方法
function setWatermark() {}
// 清除水印
const clear = () => {};
return { setWatermark, clear };
}
有了代码框架,就只需要实现函数和接口的内部实现了,另外还要考虑传参,来实现代码复用的灵活度和接口参数的可配置。
我们从具体的功能函数开始:
绘制文字背景图
这里的参数 str
就是要添加的水印文字,attr
为文字样式的属性,我们定义了属性的类型为 attr
,它包含文字的字体和大小以及颜色等值
function createBase64(str: string, attr?: attr) {
const can = document.createElement("canvas");
const width = 200;
const height = 140;
Object.assign(can, { width, height });
const cans = can.getContext("2d");
if (cans) {
cans.rotate((-20 * Math.PI) / 120);
cans.font = attr?.font ?? "12px Reggae One";
cans.fillStyle = attr?.fillStyle ?? "rgba(0, 0, 0, 0.12)";
cans.textAlign = "left";
cans.textBaseline = "middle";
cans.fillText(str, width / 20, height);
}
return can.toDataURL("image/png");
}
type attr = {
font?: string;
fillStyle?: string;
};
绘制水印层
这个函数的主要逻辑是先判断如果已经绘制了水印层,直接调用更新水印方法,如果还没有,先动态创建一个 DIV 层,设置绝对定位,铺满当前整个浏览器窗口。
const id = domSymbol.toString();
const watermarkEl = shallowRef();
const createWatermark = (str: string, attr?: attr) => {
if (unref(watermarkEl)) {
updateWatermark({ str, attr });
return id;
}
const div = document.createElement("div");
watermarkEl.value = div;
div.id = id;
div.style.pointerEvents = "none";
div.style.top = "0px";
div.style.left = "0px";
div.style.position = "absolute";
div.style.zIndex = "100000";
const el = unref(appendEl);
if (!el) return id;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ str, width, height, attr });
el.appendChild(div);
return id;
};
更新水印
因为更新水印方法主要是根据当前窗口高度和宽度来的更新水印背景的设置,利用一张 Base64 格式的图片平铺即可。
function updateWatermark(
options: {
width?: number;
height?: number;
str?: string;
attr?: attr;
} = {}
) {
const el = unref(watermarkEl);
if (!el) return;
if (options.width !== "undefined") {
el.style.width = `${options.width}px`;
}
if (ioptions.height !== "undefined") {
el.style.height = `${options.height}px`;
}
if (options.str !== "undefined") {
el.style.background = `url(${createBase64(
options.str,
options.attr
)}) left top repeat`;
}
}
到此,我们实现了主要的三个功能函数,下面就是两个对外接口:
设置水印
这里的主要点是考虑设置页面resize监听,来及时更新水印的位置。还要考虑 Vue 的生命周期,当我们卸载页面的时候要进行清除水印。
function setWatermark(str: string, attr?: attr) {
createWatermark(str, attr);
addResizeListener(document.documentElement, func);
const instance = getCurrentInstance();
if (instance) {
onBeforeUnmount(() => {
clear();
});
}
}
const func = throttle(function () {
const el = unref(appendEl);
if (!el) return;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ height, width });
});
清除水印
清除水印的时候顺便移除窗口大小监听函数
const clear = () => {
const domId = unref(watermarkEl);
watermarkEl.value = undefined;
const el = unref(appendEl);
if (!el) return;
domId && el.removeChild(domId);
removeResizeListener(el, func);
};
水印功能 hooks 的使用
import { useWatermark } from "/@/hooks/watermark";
const { setWatermark, clear } = useWatermark();
onMounted(() => {
nextTick(() => {
setWatermark(watermarkText.value);
});
});
onBeforeUnmount(() => {
clear();
});
至此,Vue3 版的网页水印功能实现全部完成。这里水印的字体大小、颜色和排布参考了企业微信的背景水印,使得看起来不那么突兀。