199 lines
6.1 KiB
Vue
199 lines
6.1 KiB
Vue
|
|
<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({
|
|||
|
|
// 文本内容,类型为字符串,必须提供,默认值为'张苹果博客'
|
|||
|
|
text: {
|
|||
|
|
type: Array,
|
|||
|
|
required: true,
|
|||
|
|
default: []
|
|||
|
|
},
|
|||
|
|
// 字体大小,类型为数字,默认值为10
|
|||
|
|
fontSize: {
|
|||
|
|
type: Number,
|
|||
|
|
default: 15
|
|||
|
|
},
|
|||
|
|
// 间距,类型为数字,默认值为2
|
|||
|
|
gap: {
|
|||
|
|
type: Number,
|
|||
|
|
default: 2
|
|||
|
|
},
|
|||
|
|
// 颜色,类型为字符串,默认值为' rgba(136, 135, 135, 0.58)'
|
|||
|
|
color: {
|
|||
|
|
type: String,
|
|||
|
|
default: "rgba(136, 135, 135, 0.58)"
|
|||
|
|
},
|
|||
|
|
//角度 类型为数字 默认值 -45
|
|||
|
|
angle: {
|
|||
|
|
type: Number,
|
|||
|
|
default: -45
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
/**
|
|||
|
|
* 绘制多行文本
|
|||
|
|
* @param ctx canvas对象
|
|||
|
|
* @param words 文本数组
|
|||
|
|
* @param x 坐标
|
|||
|
|
* @param y 坐标(行高)
|
|||
|
|
* @param maxWidth 最大宽度
|
|||
|
|
*/
|
|||
|
|
function drawTextWithWrap(ctx, words, x, y, maxWidth) {
|
|||
|
|
let line = "";
|
|||
|
|
const row = JSON.parse(JSON.stringify(y)); //拷贝一份Y的 行间距
|
|||
|
|
for (let n = 0; n < words.length; n++) {
|
|||
|
|
const testLine = line + words[n];
|
|||
|
|
const metrics = ctx.measureText(testLine);
|
|||
|
|
const testWidth = metrics.width;
|
|||
|
|
if (testWidth > maxWidth) {
|
|||
|
|
ctx.fillText(line, x, y);
|
|||
|
|
line = words[n];
|
|||
|
|
y = row * (n + 1);
|
|||
|
|
} else {
|
|||
|
|
line = testLine;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
ctx.fillText(line, x, y);
|
|||
|
|
}
|
|||
|
|
// 定义一个用于绘制水印的函数,这里可以封装一下单独引入。
|
|||
|
|
// 它是一个计算属性,意味着它的值会根据其依赖的 props 的变化而自动重新计算
|
|||
|
|
const waterMarkBg = (props) => {
|
|||
|
|
return computed(() => {
|
|||
|
|
// 创建一个新的 canvas 元素
|
|||
|
|
const canvas = document.createElement("canvas");
|
|||
|
|
// 获取设备的像素比,如果未定义则默认为1
|
|||
|
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|||
|
|
// 根据像素比计算字体大小
|
|||
|
|
const fontSize = props.fontSize * devicePixelRatio;
|
|||
|
|
// 设置字体样式
|
|||
|
|
const font = fontSize + "px serif";
|
|||
|
|
// 获取 canvas 的 2D 渲染上下文
|
|||
|
|
const ctx = canvas.getContext("2d");
|
|||
|
|
// 设置字体
|
|||
|
|
ctx.font = font;
|
|||
|
|
// 测量文本的宽度
|
|||
|
|
// const { width } = ctx.measureText(props.text);
|
|||
|
|
// 计算 canvas 的大小,至少为 60,并根据文本宽度和间距因子进行调整
|
|||
|
|
const canvasSize = Math.max(60, 180) * props.gap + devicePixelRatio;
|
|||
|
|
// 设置 canvas 的宽高
|
|||
|
|
canvas.width = canvasSize;
|
|||
|
|
canvas.height = canvasSize;
|
|||
|
|
// 将 canvas 的原点移动到中心
|
|||
|
|
ctx.translate(canvas.width / 5, canvas.height / 5);
|
|||
|
|
// 旋转 canvas 45 度
|
|||
|
|
ctx.rotate((Math.PI / 180) * props.angle);
|
|||
|
|
// 设置填充颜色
|
|||
|
|
ctx.fillStyle = props.color;
|
|||
|
|
// 设置文本对齐方式和基线
|
|||
|
|
ctx.textAlign = "center";
|
|||
|
|
ctx.textBaseline = "middle";
|
|||
|
|
// 再次设置字体
|
|||
|
|
ctx.font = font;
|
|||
|
|
drawTextWithWrap(ctx, props.text, 0, 25, 100);
|
|||
|
|
// 在 canvas 上填充文本
|
|||
|
|
// ctx.fillText(props.text, 0, 0);
|
|||
|
|
|
|||
|
|
// 返回一个对象,包含 base64 编码的图片数据、canvas 的大小和样式尺寸
|
|||
|
|
return {
|
|||
|
|
base64: canvas.toDataURL(),
|
|||
|
|
size: canvasSize,
|
|||
|
|
styleSize: canvasSize / devicePixelRatio
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 用于存储 MutationObserver 的变量
|
|||
|
|
let ob;
|
|||
|
|
// 用于存储水印 div 的变量
|
|||
|
|
let div;
|
|||
|
|
// 调用 waterMarkBg 函数获取水印相关的计算属性
|
|||
|
|
const bg = waterMarkBg(props);
|
|||
|
|
// 创建一个 ref 用于存储水印容器的 DOM 引用
|
|||
|
|
const watermarkContainerRef = ref("");
|
|||
|
|
// 创建一个 ref 用于标记水印是否需要重新生成
|
|||
|
|
const flag = ref(0);
|
|||
|
|
|
|||
|
|
// 在组件挂载后执行
|
|||
|
|
onMounted(() => {
|
|||
|
|
// 创建一个新的 MutationObserver,用于监听水印容器的变化
|
|||
|
|
ob = new MutationObserver((records) => {
|
|||
|
|
// 遍历所有的变化记录
|
|||
|
|
for (const record of records) {
|
|||
|
|
// 遍历所有被移除的节点
|
|||
|
|
for (const dom of record.removedNodes) {
|
|||
|
|
// 如果被移除的节点是水印 div,则更新 flag 的值并返回
|
|||
|
|
if (dom === div) {
|
|||
|
|
flag.value++;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果变化的节点就是水印 div,则更新 flag 的值并返回
|
|||
|
|
if (record.target === div) {
|
|||
|
|
flag.value++;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// 包括子节点的变化、属性的变化以及子树的变化
|
|||
|
|
ob.observe(watermarkContainerRef.value, {
|
|||
|
|
childList: true,
|
|||
|
|
attributes: true,
|
|||
|
|
subtree: true
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
//卸载
|
|||
|
|
onUnmounted(() => {
|
|||
|
|
ob && ob.disconnect();
|
|||
|
|
div = null;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 生成水印
|
|||
|
|
watchEffect(() => {
|
|||
|
|
// 触发 watchEffect 的重新执行
|
|||
|
|
flag.value;
|
|||
|
|
// 如果水印容器没有值,则直接返回,不执行后续操作
|
|||
|
|
if (!watermarkContainerRef.value) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// 如果之前已经存在水印 div,则先移除它
|
|||
|
|
if (div) {
|
|||
|
|
div.remove();
|
|||
|
|
}
|
|||
|
|
// 创建一个新的 div 元素用于作为水印的容器
|
|||
|
|
div = document.createElement("div");
|
|||
|
|
// 从计算属性 bg 中获取 base64 编码的图片数据和样式尺寸
|
|||
|
|
const { base64, styleSize } = bg.value;
|
|||
|
|
// 设置 div 的背景图片为水印图片的 base64 编码
|
|||
|
|
div.style.backgroundImage = `url(${base64})`;
|
|||
|
|
// 设置背景图片的尺寸
|
|||
|
|
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
|
|||
|
|
// 设置背景图片重复显示
|
|||
|
|
div.style.backgroundRepeat = "repeat";
|
|||
|
|
// 设置水印 div 的 z-index 为 9999,以确保它显示在大多数其他元素之上
|
|||
|
|
div.style.zIndex = 9999;
|
|||
|
|
// 设置水印 div 不响应鼠标事件,如点击、悬停等
|
|||
|
|
div.style.pointerEvents = "none";
|
|||
|
|
// 设置水印 div 的位置为绝对定位
|
|||
|
|
div.style.position = "absolute";
|
|||
|
|
// 使用 inset 属性设置 div 占据整个父容器的空间
|
|||
|
|
div.style.inset = "0";
|
|||
|
|
// 将水印 div 添加到水印容器中
|
|||
|
|
watermarkContainerRef.value.appendChild(div);
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.watermark-container {
|
|||
|
|
position: relative;
|
|||
|
|
height: 100vh;
|
|||
|
|
color: rgba(136, 135, 135, 0.58);
|
|||
|
|
}
|
|||
|
|
</style>
|