说到前端做水印吧,它其实更大的作用是做一个提示,告诉你这一块儿有版权,起到一个警示的作用和一个简单的防篡改;真正的要实现安全保护,往往都是技术手段和非技术手段双管齐下才能达到一个很好的效果。前端做水印最大的优点在于它成本低,只需要编译一个组件就完了,甚至在某一些组件库里本身就自带这个组件,像这个Ant Design里边有个Watermark,用这个组件可以非常轻松的实现一个文字水印,不过遗憾的是在Vue的组件库里面是没有这个组件的,我们需要手动实现这个组件!
React中使用
antd版本要大于5: "antd": "^5.20.5",
vue项目要实现这种效果
vue3中手动实现
<template>
<div class="watermark-container" ref="parentRef">
<slot></slot>
</div>
</template>
<script setup>
import useWatermarkBg from './useWatermarkBg';
import defineProps, { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({
text: {
type: String,
required: true,
default: 'watermark'
},
fontSize: {
type: Number,
default: 40
},
gap: {
type: Number,
default: 20
},
});
const bg = useWatermarkBg(props);
const parentRef = ref(null);
let div;
// 重置水印
function resetWatermark() {
if(!parentRef.value) return;
if (div) {
div.remove();
}
const { base64, size } = bg.value;
div = document.createElement('div');
div.style.position = 'absolute';
div.style.backgroundImage = `url(${base64})`;
div.style.backgroundSize = `${size}px ${size}px`;
div.style.backgroundRepeat = 'repeat';
div.style.zIndex = 9999;
div.style.inset = 0;
parentRef.value.appendChild(div);
}
onMounted(resetWatermark);
// 监听操作变化
const ob = new MutationObserver((entries) => {
for (const entry of entries) {
// 删除
for(const dom of entry.removedNodes) {
if(dom === div) {
console.log('水印被删除');
resetWatermark();
return;
}
}
// 修改
if(entry.target === div) {
console.log('水印被修改');
resetWatermark();
return;
}
}
})
onMounted(() => {
ob.observe(parentRef.value, {
childList: true,
subtree: true,
attributes: true,
})
})
onUnmounted(() => {
// 取消监听
ob.disconnect();
})
</script>
<style scoped>
.watermark-container {
position: relative;
}
</style>
vue2中手动实现
<template>
<div class="container">
<watermark text="版权所有" id="copyright">
<div class="content">内容</div>
</watermark>
</div>
</template>
<script>
import watermark from "@/components/watermark.vue";
export default {
props: {},
components: {
watermark,
},
};
</script>
<style scoped lang="less">
.container {
display: flex;
width: 100%;
justify-content: space-around;
.content {
width: 400px;
height: 400px;
background-color: antiquewhite;
}
}
</style>
组件watermark.vue
<template>
<div class="watermark-container" ref="parentRef">
<slot></slot>
</div>
</template>
<script>
import useWatermarkBg from './useWatermarkBg';
let div, bg, parentRef;
const ob = new MutationObserver((entries) => {
for (const entry of entries) {
// 删除
for(const dom of entry.removedNodes) {
if(dom === div) {
console.log('水印被删除');
resetWatermark();
return;
}
}
// 修改
if(entry.target === div) {
console.log('水印被修改');
resetWatermark();
return;
}
}
})
function resetWatermark() {
if(!parentRef) return;
if (div) {
div.remove();
}
const { base64, size } = bg.value;
div = document.createElement('div');
div.style.position = 'absolute';
div.style.backgroundImage = `url(${base64})`;
div.style.backgroundSize = `${size}px ${size}px`;
div.style.backgroundRepeat = 'repeat';
div.style.zIndex = 9999;
div.style.inset = 0;
parentRef.appendChild(div);
}
export default {
props: {
text: {
type: String,
required: true,
default: 'watermark'
},
fontSize: {
type: Number,
default: 40
},
gap: {
type: Number,
default: 20
},
},
data() {
return {
key: 'value',
}
},
mounted () {
bg = useWatermarkBg(this._props);
parentRef = this.$refs['parentRef']
console.log('this.$refs', this.$refs);
if(parentRef) {
ob.observe(parentRef, {
childList: true,
subtree: true,
attributes: true,
})
}
resetWatermark();
},
destroyed () {
ob.disconnect();
},
}
</script>
<style scoped>
.watermark-container {
position: relative;
}
</style>
画出水印,使用useWatermarkBj.js
import { computed } from "vue";
export default function useWatermarkBg(props){
return computed(() => {
const canvas = document.createElement('canvas');
const devicePixelRatio = window.devicePixelRatio || 1;
const fontSize = props.fontSize * devicePixelRatio;
const font = fontSize + 'px serif'
const ctx = canvas.getContext('2d')
// 获取文字宽度
ctx.font = font;
const { width } = ctx.measureText(props.text)
const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio;
canvas.width = canvasSize;
canvas.height = canvasSize;
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate((Math.PI / 180) * -45);
ctx.fillStyle ='rgba(0, 0, 0, 0.3)';
ctx.font = font;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(props.text, 0, 0);
return {
base64: canvas.toDataURL(),
size: canvasSize / devicePixelRatio,
}
});
}
vue2/vue3用法略有差异,最终效果都可以实现文字水印的功能,但是vue2使用时如果是同一个页面多次使用同一个组件,因为使用的是同一个实例,所以会有作用域污染的问题,只渲染一个,具体解决办法,目前还在思考,欢迎大佬评论指导!