1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| <template> <div ref="containerRef" :style="{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, overflow: 'hidden', // 防止旋转的文本溢出导致滚动条 zIndex: props.zIndex, pointerEvents: 'none', // 容器本身也应允许事件穿透 }" > <div v-for="wm in watermarkItems" :key="wm.id" :style="wm.style" > {{ props.text }} </div> </div> </template>
<script setup> import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
// 1. 定义 Props,与 React 版本保持一致 const props = defineProps({ text: { type: String, default: "Mr.彭涛", }, textColor: { type: String, default: "rgba(180, 180, 180, 0.6)", // 与React代码中的默认值一致 }, fontSize: { type: Number, default: 18, }, angle: { type: Number, default: -30, }, rowHeight: { type: Number, default: 150, }, zIndex: { type: Number, default: -1, }, });
// 2. 定义响应式状态和模板引用 const watermarkItems = ref([]); // 存储计算出的水印项 const containerRef = ref(null); // 用于获取容器DOM元素的引用
// 3. 水印计算逻辑 (与React版本核心逻辑相同) const calculateAndSetWatermarks = () => { if (!containerRef.value) { // 容器DOM元素尚未准备好 return; }
const { clientWidth, clientHeight } = containerRef.value; if (clientWidth === 0 || clientHeight === 0) { // 容器尺寸为0,无法计算 return; }
const newItems = []; const numEffectiveRows = Math.ceil(clientHeight / props.rowHeight); let itemKey = 0;
for (let i = 0; i < numEffectiveRows; i++) { const isFourItemsRow = i % 2 === 0; const itemsInThisRow = isFourItemsRow ? 4 : 3; const currentY = (i + 0.5) * props.rowHeight;
for (let j = 0; j < itemsInThisRow; j++) { const currentXPercent = ((j + 0.5) / itemsInThisRow) * 100; newItems.push({ id: `staggered-wm-${itemKey++}`, style: { position: 'absolute', top: `${currentY}px`, left: `${currentXPercent}%`, transform: `translate(-50%, -50%) rotate(${props.angle}deg)`, fontSize: `${props.fontSize}px`, color: props.textColor, whiteSpace: 'nowrap', userSelect: 'none', pointerEvents: 'none', }, }); } } watermarkItems.value = newItems; };
// 4. 处理生命周期和响应式更新 let resizeObserverInstance = null;
onMounted(() => { // 组件挂载后,DOM元素可用 // 使用 nextTick 确保在DOM完全渲染和尺寸计算稳定后再执行初次计算 // 这类似于React中useEffect内使用setTimeout(fn, 0)的效果 nextTick(() => { calculateAndSetWatermarks(); });
// 创建并启动 ResizeObserver if (containerRef.value) { resizeObserverInstance = new ResizeObserver(() => { // 当容器尺寸变化时,重新计算水印 calculateAndSetWatermarks(); }); resizeObserverInstance.observe(containerRef.value); } });
onUnmounted(() => { // 组件卸载前,清理 ResizeObserver if (resizeObserverInstance) { if (containerRef.value) { // 确保元素仍存在,尽管通常observer会自己处理 resizeObserverInstance.unobserve(containerRef.value); } resizeObserverInstance.disconnect(); // 更彻底的清理 resizeObserverInstance = null; } });
// 监听影响布局的props的变化 watch( // 监听的源:一个返回包含所有相关props的数组的getter函数 () => [props.text, props.textColor, props.fontSize, props.angle, props.rowHeight], () => { // 当任何一个被监听的prop变化时,重新计算水印 // 同样使用nextTick,以防prop变化引起DOM reflow影响尺寸读取 nextTick(() => { calculateAndSetWatermarks(); }); }, { // deep: false, // 对于这些基本类型和顶层对象属性,不需要深度监听 // immediate: false // 不在watcher创建时立即执行,onMounted已处理首次加载 } );
</script>
<style scoped>
</style>
|