基于Vue 3的全新水印通用組件。這款組件不僅功能強(qiáng)大,而且易于集成,能夠輕松為您的網(wǎng)頁或應(yīng)用添加自定義水印,有效防止內(nèi)容被篡改或盜用。
一,編寫watermark組件
<template>
<div ref="watermarkContainerRef" class="watermark-container">
<!-- 插槽-->
<slot></slot>
</div>
</template>
<script setup>
import { ref, onMounted, watchEffect, onUnmounted, computed } from "vue";
// 使用 defineProps 定義一個組件的 props,這些 props 描述了組件從父組件接收的屬性
const props = defineProps({
// 文本內(nèi)容,類型為字符串,必須提供,默認(rèn)值為'張?zhí)O果博客'
text: {
type: String,
required: true,
default: '張?zhí)O果博客'
},
// 字體大小,類型為數(shù)字,默認(rèn)值為10
fontSize: {
type: Number,
default: 10,
},
// 間距,類型為數(shù)字,默認(rèn)值為1
gap: {
type: Number,
default: 1,
},
// 顏色,類型為字符串,默認(rèn)值為'rgba(82,75,75,0.58)'
color: {
type: String,
default: 'rgba(82,75,75,0.58)',
}
});
// 定義一個用于繪制水印的函數(shù),這里可以封裝一下單獨引入。
// 它是一個計算屬性,意味著它的值會根據(jù)其依賴的 props 的變化而自動重新計算
const waterMarkBg = (props) => {
return computed(() => {
// 創(chuàng)建一個新的 canvas 元素
const canvas = document.createElement("canvas");
// 獲取設(shè)備的像素比,如果未定義則默認(rèn)為1
const devicePixelRatio = window.devicePixelRatio || 1;
// 根據(jù)像素比計算字體大小
const fontSize = props.fontSize * devicePixelRatio;
// 設(shè)置字體樣式
const font = fontSize + "px serif";
// 獲取 canvas 的 2D 渲染上下文
const ctx = canvas.getContext("2d");
// 設(shè)置字體
ctx.font = font;
// 測量文本的寬度
const { width } = ctx.measureText(props.text);
// 計算 canvas 的大小,至少為 60,并根據(jù)文本寬度和間距因子進(jìn)行調(diào)整
const canvasSize = Math.max(60, width) * props.gap + devicePixelRatio;
// 設(shè)置 canvas 的寬高
canvas.width = canvasSize;
canvas.height = canvasSize;
// 將 canvas 的原點移動到中心
ctx.translate(canvas.width / 2, canvas.height / 2);
// 旋轉(zhuǎn) canvas 45 度
ctx.rotate((Math.PI / 180) * -45);
// 設(shè)置填充顏色
ctx.fillStyle = props.color;
// 設(shè)置文本對齊方式和基線
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 再次設(shè)置字體
ctx.font = font;
// 在 canvas 上填充文本
ctx.fillText(props.text, 0, 0);
// 返回一個對象,包含 base64 編碼的圖片數(shù)據(jù)、canvas 的大小和樣式尺寸
return {
base64: canvas.toDataURL(),
size: canvasSize,
styleSize: canvasSize / devicePixelRatio
};
});
};
// 用于存儲 MutationObserver 的變量
let ob;
// 用于存儲水印 div 的變量
let div;
// 調(diào)用 waterMarkBg 函數(shù)獲取水印相關(guān)的計算屬性
const bg = waterMarkBg(props);
// 創(chuàng)建一個 ref 用于存儲水印容器的 DOM 引用
const watermarkContainerRef = ref('');
// 創(chuàng)建一個 ref 用于標(biāo)記水印是否需要重新生成
const flag = ref(0);
// 在組件掛載后執(zhí)行
onMounted(() => {
// 創(chuàng)建一個新的 MutationObserver,用于監(jiān)聽水印容器的變化
ob = new MutationObserver((records) => {
// 遍歷所有的變化記錄
for (const record of records) {
// 遍歷所有被移除的節(jié)點
for (const dom of record.removedNodes) {
// 如果被移除的節(jié)點是水印 div,則更新 flag 的值并返回
if (dom === div) {
flag.value++;
return;
}
}
// 如果變化的節(jié)點就是水印 div,則更新 flag 的值并返回
if (record.target === div) {
flag.value++;
return;
}
}
});
// 包括子節(jié)點的變化、屬性的變化以及子樹的變化
ob.observe(watermarkContainerRef.value,{
childList:true,
attributes:true,
subtree:true
});
})
//卸載
onUnmounted(()=>{
ob && ob.disconnect();
div=null;
})
// 生成水印
watchEffect(() => {
// 觸發(fā) watchEffect 的重新執(zhí)行
flag.value;
// 如果水印容器沒有值,則直接返回,不執(zhí)行后續(xù)操作
if (!watermarkContainerRef.value) {
return;
}
// 如果之前已經(jīng)存在水印 div,則先移除它
if (div) {
div.remove();
}
// 創(chuàng)建一個新的 div 元素用于作為水印的容器
div = document.createElement('div');
// 從計算屬性 bg 中獲取 base64 編碼的圖片數(shù)據(jù)和樣式尺寸
const { base64, styleSize } = bg.value;
// 設(shè)置 div 的背景圖片為水印圖片的 base64 編碼
div.style.backgroundImage = `url(${base64})`;
// 設(shè)置背景圖片的尺寸
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
// 設(shè)置背景圖片重復(fù)顯示
div.style.backgroundRepeat = "repeat";
// 設(shè)置水印 div 的 z-index 為 9999,以確保它顯示在大多數(shù)其他元素之上
div.style.zIndex = 9999;
// 設(shè)置水印 div 不響應(yīng)鼠標(biāo)事件,如點擊、懸停等
div.style.pointerEvents = "none";
// 設(shè)置水印 div 的位置為絕對定位
div.style.position = "absolute";
// 使用 inset 屬性設(shè)置 div 占據(jù)整個父容器的空間
div.style.inset = "0";
// 將水印 div 添加到水印容器中
watermarkContainerRef.value.appendChild(div);
});
</script>
<style scoped>
.watermark-container{
position: relative;
}
</style>
二,在頁面中引入使用
<template>
<div>
<n-grid>
<n-gi style="margin: 15px" span="6 1025:2 " v-for="(item,index) in 4" :key="index">
<!-- 引入 Watermark-->
<Watermark :gap="gap" :text="text" :fontSize="fontSize" :color="color">
<n-card
v-motion-pop-visible-once
title="標(biāo)題"
hoverable
>
這是內(nèi)容 <br>
這是內(nèi)容 <br>
這是內(nèi)容 <br>
這是內(nèi)容 <br>
這是內(nèi)容 <br>
這是內(nèi)容 <br>
</n-card>
</Watermark>
</n-gi>
</n-grid>
</div>
</template>
<script setup>
import Watermark from '../components/Watermark.vue'
import {ref} from "vue";
const text=ref('張?zhí)O果博客');
const gap=ref(1);
const fontSize=ref('10');
const color=ref('');
</script>
<style scoped>
</style>