需求:写一个水印组件 父组件传递一个watermark的对象数据,子组件接受数据,并显示。 子组件代码如下:
<template>
<div
class="watermark-container"
:style="{
position: 'relative',
// zIndex: 9999,
}"
>
<slot></slot>
<div
class="watermark"
:style="{
position: 'absolute',
top: watermark.top + 'px',
width: `${width}px`,
height: `${height}px`,
pointerEvents: 'none',
zIndex: 9999,
}"
>
<canvas
ref="canvasRef"
:style="{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
transform: `rotate(${watermark.rotate}deg)`,
}"
></canvas>
</div>
</div>
</template>
<script setup>
import { watch } from 'vue';
import { ref, onMounted, computed, onUnmounted } from 'vue';
const props = defineProps({
watermark: {
type: Object,
default: true,
},
});
const canvasRef = ref(null);
watch(
() => props.watermark,
() => {
const elImage = document.querySelector('.watermark-container .el-image');
width.value = elImage.offsetWidth;
// 获取元素的高度
height.value = elImage.offsetHeight;
drawWatermark();
},
{ deep: true }
);
const width = ref('');
const height = ref('');
onMounted(() => {
// /
// 获取页面上元素el-image的宽高
const elImage = document.querySelector('.watermark-container .el-image');
// 获取元素的宽度
width.value = elImage.offsetWidth;
// 获取元素的高度
height.value = elImage.offsetHeight;
});
const canvasWidth = computed(() => {
return window.innerWidth - props.leftOffset - props.rightOffset;
});
const canvasHeight = computed(() => {
return window.innerHeight - props.topOffset - props.bottomOffset;
});
const drawWatermark = async () => {
const canvas = canvasRef.value;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (props.watermark.text) {
ctx.font = `${props.watermark.fontSize}px Arial`;
ctx.fillStyle = props.watermark.color;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
for (let x = 0; x < canvas.width; x += props.watermark.xSpace) {
for (let y = 0; y < canvas.height; y += props.watermark.ySpace) {
ctx.save();
ctx.translate(x, y);
ctx.rotate((props.watermark.rotate * Math.PI) / 180);
ctx.fillText(props.watermark.text, 0, 0);
ctx.restore();
}
}
} else if (props.watermark.imgUrl) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = props.watermark.imgUrl;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
ctx.globalAlpha = props.watermark.alpha;
for (let x = 0; x < canvas.width; x += props.watermark.xSpace) {
for (let y = 0; y < canvas.height; y += props.watermark.ySpace) {
ctx.save();
ctx.translate(x, y);
ctx.rotate((props.watermark.rotate * Math.PI) / 180);
ctx.drawImage(img, 0, 0, props.watermark.fontSize, 10);
ctx.restore();
}
}
}
};
onMounted(() => {
drawWatermark();
window.addEventListener('resize', drawWatermark);
});
onUnmounted(() => {
window.removeEventListener('resize', drawWatermark);
});
</script>
<style scoped lang="scss">
.watermark-container {
width: 100%;
height: 100%;
.el-image {
width: 100%;
}
}
.watermark {
overflow: hidden;
}
</style>
根据父组件中传递过来的时text还是img,循环添加水印,根据父组件中的xSpace和ySpacs分别代表水平间距和垂直间距画图像。 以下是父组件使用水印组件的方法,将img标签插入子组件的slot插槽中进行使用
<Watermark :watermark="{ text: '水印文本', fontSize: 20, color: 'gray', rotate: -30, xSpace: 100, ySpace: 150 }">
<!-- 这里的内容会被插入到子组件的 <slot> 位置 -->
<img src="example.jpg" alt="示例图片" class="el-image" />
</Watermark>