284 lines
5.3 KiB
Plaintext
284 lines
5.3 KiB
Plaintext
<template>
|
|
<view class="cl-watermark" :class="[pt.className]">
|
|
<view class="cl-watermark__content" :class="[pt.container?.className]">
|
|
<slot></slot>
|
|
</view>
|
|
|
|
<canvas
|
|
ref="canvasRef"
|
|
type="2d"
|
|
:canvas-id="canvasId"
|
|
:id="canvasId"
|
|
class="cl-watermark__canvas"
|
|
:style="{
|
|
width: containerWidth + 'px',
|
|
height: containerHeight + 'px',
|
|
zIndex
|
|
}"
|
|
></canvas>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {
|
|
ref,
|
|
computed,
|
|
onMounted,
|
|
watch,
|
|
getCurrentInstance,
|
|
nextTick,
|
|
shallowRef,
|
|
onUnmounted
|
|
} from "vue";
|
|
import { getDevicePixelRatio, isDark, parsePt, uuid } from "@/cool";
|
|
import type { PassThroughProps } from "../../types";
|
|
|
|
defineOptions({
|
|
name: "cl-watermark"
|
|
});
|
|
|
|
const props = defineProps({
|
|
// 透传样式
|
|
pt: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
// 水印文本
|
|
text: {
|
|
type: String,
|
|
default: "Watermark"
|
|
},
|
|
// 水印字体大小
|
|
fontSize: {
|
|
type: Number,
|
|
default: 16
|
|
},
|
|
// 水印文字颜色(浅色模式)
|
|
color: {
|
|
type: String,
|
|
default: "rgba(0, 0, 0, 0.15)"
|
|
},
|
|
// 水印文字颜色(深色模式)
|
|
darkColor: {
|
|
type: String,
|
|
default: "rgba(255, 255, 255, 0.15)"
|
|
},
|
|
// 水印透明度
|
|
opacity: {
|
|
type: Number,
|
|
default: 1
|
|
},
|
|
// 水印旋转角度
|
|
rotate: {
|
|
type: Number,
|
|
default: -22
|
|
},
|
|
// 水印宽度
|
|
width: {
|
|
type: Number,
|
|
default: 120
|
|
},
|
|
// 水印高度
|
|
height: {
|
|
type: Number,
|
|
default: 64
|
|
},
|
|
// 水印之间的水平间距
|
|
gapX: {
|
|
type: Number,
|
|
default: 100
|
|
},
|
|
// 水印之间的垂直间距
|
|
gapY: {
|
|
type: Number,
|
|
default: 100
|
|
},
|
|
// 水印层级
|
|
zIndex: {
|
|
type: Number,
|
|
default: 9
|
|
},
|
|
// 字体粗细
|
|
fontWeight: {
|
|
type: String,
|
|
default: "normal"
|
|
},
|
|
// 字体样式
|
|
fontFamily: {
|
|
type: String,
|
|
default: "sans-serif"
|
|
}
|
|
});
|
|
|
|
// 透传样式类型定义
|
|
type PassThrough = {
|
|
className?: string;
|
|
container?: PassThroughProps;
|
|
};
|
|
|
|
// 解析透传样式配置
|
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
|
|
|
// 获取当前实例
|
|
const { proxy } = getCurrentInstance()!;
|
|
|
|
// 创建canvas实例
|
|
const canvasRef = shallowRef<UniElement | null>(null);
|
|
// 创建canvas ID
|
|
const canvasId = `cl-watermark-${uuid()}`;
|
|
|
|
// 容器高度
|
|
const containerWidth = ref(0);
|
|
|
|
// 容器宽度
|
|
const containerHeight = ref(0);
|
|
|
|
// 计算当前水印颜色
|
|
const currentColor = computed(() => {
|
|
if (isDark.value) {
|
|
return props.darkColor;
|
|
}
|
|
return props.color;
|
|
});
|
|
|
|
/**
|
|
* 获取容器尺寸
|
|
*/
|
|
function getContainerSize(): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
uni.createSelectorQuery()
|
|
.in(proxy)
|
|
.select(".cl-watermark")
|
|
.boundingClientRect((rect) => {
|
|
containerHeight.value = (rect as NodeInfo).height ?? 0;
|
|
containerWidth.value = (rect as NodeInfo).width ?? 0;
|
|
|
|
resolve();
|
|
})
|
|
.exec();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 绘制水印 - 使用Canvas
|
|
*/
|
|
async function drawWatermark() {
|
|
// 获取容器尺寸
|
|
await getContainerSize();
|
|
|
|
// 等待渲染完成
|
|
await nextTick();
|
|
|
|
if (containerWidth.value <= 0 || containerHeight.value <= 0) return;
|
|
|
|
uni.createCanvasContextAsync({
|
|
id: canvasId,
|
|
component: proxy,
|
|
success: (canvasContext: CanvasContext) => {
|
|
const drawCtx = canvasContext.getContext("2d")!;
|
|
|
|
// 设置canvas尺寸
|
|
drawCtx.canvas.width = containerWidth.value;
|
|
drawCtx.canvas.height = containerHeight.value;
|
|
|
|
// 清空画布
|
|
// #ifdef APP
|
|
drawCtx.reset();
|
|
// #endif
|
|
drawCtx.clearRect(0, 0, containerWidth.value, containerHeight.value);
|
|
|
|
// 缩放画布以适配高分屏
|
|
// #ifdef APP
|
|
const ratio = getDevicePixelRatio();
|
|
drawCtx.scale(ratio, ratio);
|
|
// #endif
|
|
|
|
// 设置全局透明度
|
|
drawCtx.globalAlpha = props.opacity;
|
|
|
|
// 设置字体
|
|
drawCtx.font = `${props.fontWeight} ${props.fontSize}px ${props.fontFamily}`;
|
|
drawCtx.fillStyle = currentColor.value;
|
|
drawCtx.textAlign = "center";
|
|
drawCtx.textBaseline = "middle";
|
|
|
|
// 计算水印单元的总宽高(包含间距)
|
|
const cellWidth = props.width + props.gapX;
|
|
const cellHeight = props.height + props.gapY;
|
|
|
|
// 计算需要多少行和列
|
|
const cols = Math.ceil(containerWidth.value / cellWidth) + 1;
|
|
const rows = Math.ceil(containerHeight.value / cellHeight) + 1;
|
|
|
|
// 遍历绘制水印
|
|
for (let row = 0; row < rows; row++) {
|
|
for (let col = 0; col < cols; col++) {
|
|
const x = col * cellWidth + props.width / 2;
|
|
const y = row * cellHeight + props.height / 2;
|
|
|
|
drawCtx.save();
|
|
drawCtx.translate(x, y);
|
|
drawCtx.rotate((props.rotate * Math.PI) / 180);
|
|
drawCtx.fillText(props.text, 0, 0);
|
|
drawCtx.restore();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 监听深色模式变化
|
|
const stopWatchDark = watch(isDark, () => {
|
|
drawWatermark();
|
|
});
|
|
|
|
// 监听属性变化
|
|
const stopWatchProps = watch(
|
|
computed(() => [
|
|
props.text,
|
|
props.fontSize,
|
|
props.color,
|
|
props.darkColor,
|
|
props.opacity,
|
|
props.rotate,
|
|
props.width,
|
|
props.height,
|
|
props.gapX,
|
|
props.gapY,
|
|
props.fontWeight,
|
|
props.fontFamily
|
|
]),
|
|
() => {
|
|
drawWatermark();
|
|
}
|
|
);
|
|
|
|
onMounted(() => {
|
|
drawWatermark();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
stopWatchDark();
|
|
stopWatchProps();
|
|
});
|
|
|
|
defineExpose({
|
|
refresh: drawWatermark
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.cl-watermark {
|
|
@apply relative w-full h-full;
|
|
|
|
&__content {
|
|
@apply relative w-full h-full;
|
|
z-index: 1;
|
|
}
|
|
|
|
&__canvas {
|
|
@apply absolute top-0 left-0 pointer-events-none;
|
|
}
|
|
}
|
|
</style>
|