經(jīng)常有一些公司和組織出于系統(tǒng)文件或信息安全保密的需要呐舔,需要在系統(tǒng)網(wǎng)頁(yè)上增加帶有個(gè)人標(biāo)識(shí)(系統(tǒng)賬號(hào)或個(gè)人信息)的水印漆羔,可以簡(jiǎn)單防止截圖外傳
首先我們來(lái)看這樣一個(gè)水印功能的實(shí)現(xiàn)思路奇瘦,通常是在我們?cè)械木W(wǎng)頁(yè)上附上一個(gè) DIV 層章咧,將它設(shè)置絕對(duì)定位鋪滿整個(gè)窗口闷盔,然后 z-index 值盡量往大了設(shè)弯洗,保證讓水印層處于當(dāng)前網(wǎng)頁(yè)所有元素的上面,又不影響當(dāng)前網(wǎng)頁(yè)的操作馁筐。
水印上的字體有兩種方式添加:
- 第一種直接將字體用塊元素包裹涂召,動(dòng)態(tài)設(shè)置絕對(duì)定位,然后通過(guò) transform 屬性旋轉(zhuǎn)敏沉;
- 第二種通過(guò)在 canvas 上繪制出字體果正,設(shè)置好樣式,然后以圖片的樣式導(dǎo)出盟迟,最后用圖片作為水印層的背景圖秋泳。
處于性能方面考慮,第二種方式最優(yōu)攒菠。我們來(lái)看具體怎么實(shí)現(xiàn)迫皱?
作為一塊獨(dú)立的功能,我們?cè)?Vue3 中常用 hooks 來(lái)實(shí)現(xiàn)辖众,通過(guò)分析我們概括出實(shí)現(xiàn)水印需要的幾個(gè)功能函數(shù)和對(duì)外接口:
對(duì)外接口
- 清除水幼科稹(clear)
- 設(shè)置水印(setWatermark)
核心功能函數(shù)
- 繪制文字背景圖(createBase64)
- 繪制水印層(createWatermark)
- 頁(yè)面隨窗口大小調(diào)整更新(updateWatermark)
export function useWatermark(
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>
) {
// 繪制文字背景圖
function createBase64() {}
// 繪制水印層
const createWatermark = () => {};
// 頁(yè)面隨窗口調(diào)整更新水印
function updateWatermark(){}
// 對(duì)外提供的設(shè)置水印方法
function setWatermark() {}
// 清除水印
const clear = () => {};
return { setWatermark, clear };
}
有了代碼框架凹炸,就只需要實(shí)現(xiàn)函數(shù)和接口的內(nèi)部實(shí)現(xiàn)了戏阅,另外還要考慮傳參,來(lái)實(shí)現(xiàn)代碼復(fù)用的靈活度和接口參數(shù)的可配置啤它。
我們從具體的功能函數(shù)開始:
繪制文字背景圖
這里的參數(shù) 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;
};
繪制水印層
這個(gè)函數(shù)的主要邏輯是先判斷如果已經(jīng)繪制了水印層离赫,直接調(diào)用更新水印方法,如果還沒(méi)有塌碌,先動(dòng)態(tài)創(chuàng)建一個(gè) DIV 層渊胸,設(shè)置絕對(duì)定位,鋪滿當(dāng)前整個(gè)瀏覽器窗口台妆。
const id = domSymbol.toString();
const watermarkEl = shallowRef<HTMLElement>();
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;
};
更新水印
因?yàn)楦滤》椒ㄖ饕歉鶕?jù)當(dāng)前窗口高度和寬度來(lái)的更新水印背景的設(shè)置蹬刷,利用一張 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`;
}
}
到此,我們實(shí)現(xiàn)了主要的三個(gè)功能函數(shù)办成,下面就是兩個(gè)對(duì)外接口:
設(shè)置水印
這里的主要點(diǎn)是考慮設(shè)置頁(yè)面resize監(jiān)聽泡态,來(lái)及時(shí)更新水印的位置。還要考慮 Vue 的生命周期迂卢,當(dāng)我們卸載頁(yè)面的時(shí)候要進(jìn)行清除水印某弦。
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 });
});
清除水印
清除水印的時(shí)候順便移除窗口大小監(jiān)聽函數(shù)
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 版的網(wǎng)頁(yè)水印功能實(shí)現(xiàn)全部完成而克。這里水印的字體大小靶壮、顏色和排布參考了企業(yè)微信的背景水印,使得看起來(lái)不那么突兀员萍。