需求描述:
给整个屏幕添加水印,并且水印中的时间动态更新。
实现效果:
实现步骤:
1. 创建一个水印容器,覆盖整个屏幕,fixed定位,top:0, left:0, width和height 100%,pointer-events: none;,z-index高。
2. 在这个容器内动态生成多个水印元素,比如文本节点,或者使用canvas绘制的水印图片作为背景。
3. 使用MutationObserver来保护水印容器,防止被删除或修改。
4. 动态更新水印,比如每隔一段时间重新生成水印图片。
5.监听resize事件,在窗口大小变化时重新生成。
完整代码:
// Watermark.vue
<template>
<div ref="watermarkContainer" class="watermark-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue"
interface WatermarkOptions {
updateInterval?: number // 更新间隔
opacity?: number // 透明度
fontSize?: number // 字体大小
zIndex?: number // 层级
angle?: number // 旋转角度
colSpacing?: number //列间距(px)
rowSpacing?: number //行间距(px)
}
const props = withDefaults(defineProps<WatermarkOptions>(), {
updateInterval: 3000,
opacity: 0.2,
fontSize: 20,
zIndex: 9999,
angle: -15,
rowSpacing: 160,
colSpacing: 240
})
const watermarkContainer = ref<HTMLElement | null>(null)
let observer: MutationObserver | null = null
let updateTimer: number | null = null
// 生成水印文本
const generateText = (): string => {
const timestamp = new Date().toLocaleString()
return `百昌科技 ${timestamp} `
}
// 计算水印矩阵布局
const calculateLayout = () => {
if (!watermarkContainer.value) return []
const { clientWidth: width, clientHeight: height } = document.documentElement
const colCount = Math.ceil(width / props.colSpacing) + 1
const rowCount = Math.ceil(height / props.rowSpacing) + 1
return Array.from({ length: rowCount }, (_, row) =>
Array.from({ length: colCount }, (_, col) => ({
x: col * props.colSpacing,
y: row * props.rowSpacing
}))
).flat()
}
// 创建单个水印元素
const createWatermark = (position: { x: number; y: number }): HTMLElement => {
const watermark = document.createElement("div")
watermark.className = "watermark"
Object.assign(watermark.style, {
left: `${position.x}px`,
top: `${position.y}px`,
transform: `translate(-50%, -50%) rotate(${props.angle}deg)`,
opacity: props.opacity.toString(),
fontSize: `${props.fontSize}px`,
position: "absolute",
whiteSpace: "nowrap",
userSelect: "none",
pointerEvents: "none",
color: "#666"
})
watermark.textContent = generateText()
return watermark
}
// 生成水印群组
const generateWatermarks = (): void => {
if (!watermarkContainer.value) return
watermarkContainer.value.innerHTML = ""
const positions = calculateLayout()
positions.forEach((position) => {
watermarkContainer.value?.appendChild(createWatermark(position))
})
}
// 动态更新水印
const updateWatermarks = (): void => {
if (!watermarkContainer.value) return
const watermarks = watermarkContainer.value.querySelectorAll(".watermark")
watermarks.forEach((w) => {
w.textContent = generateText()
})
}
// 初始化防删除观察者
const initObserver = (): void => {
observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (!document.body.contains(watermarkContainer.value)) {
document.body.appendChild(watermarkContainer.value!)
generateWatermarks()
}
Array.from(mutation.removedNodes).forEach((node) => {
if (node === watermarkContainer.value) {
document.body.appendChild(watermarkContainer.value!)
generateWatermarks()
}
})
})
})
observer.observe(document.body, {
childList: true,
subtree: true
})
}
// 初始化水印
const initWatermark = (): void => {
if (!watermarkContainer.value) return
// 设置容器样式
Object.assign(watermarkContainer.value.style, {
position: "fixed",
top: "0",
left: "0",
width: "100vw",
height: "100vh",
pointerEvents: "none",
zIndex: props.zIndex.toString(),
overflow: "hidden"
})
// 防止控制台删除
Object.defineProperty(window, "watermarkContainer", {
configurable: false,
writable: false,
value: watermarkContainer.value
})
generateWatermarks()
initObserver()
}
onMounted(() => {
initWatermark()
updateTimer = setInterval(updateWatermarks, props.updateInterval)
window.addEventListener("resize", generateWatermarks)
})
onBeforeUnmount(() => {
if (observer) observer.disconnect()
if (updateTimer) clearInterval(updateTimer)
window.removeEventListener("resize", generateWatermarks)
})
</script>
<style scoped>
.watermark-container :deep(.watermark) {
position: absolute;
opacity: v-bind("props.opacity");
color: #666;
font-size: v-bind('props.fontSize + "px"');
user-select: none;
white-space: nowrap;
animation: move 20s linear infinite;
}
/* @keyframes move {
0% {
transform: translate(-50%, -50%);
}
100% {
transform: translate(150%, 150%);
}
} */
</style>
import Watermark from "../components/Watermark.vue
<Watermark :angle="-15" :row-spacing="180" :col-spacing="300" :opacity="0.18" :update-interval="5000" />