效果
水印的作用
图片加水印的操作一般是由后端来完成,有些站点保护的知识产权的类型可能比较多,不仅仅是图片,可能还有视频、文字等等,对于不同类型的对象添加水印后端操作比较复杂,所有有些站点逐步的让前端去进行水印添加的操作。
前端框架
React框架
如果用React
框架来进行开发就比较简单,在Ant Design 库里面有一个水印组件Watermark
,通过这个组件你可以给一个区域加上一个水印。区域内容没有限制,如图片、文字、视频等等添加水印都是可以的。
Vue 框架
Ant Design Vue 和 Element UI 暂时没有水印组件。所以需要自己开发,基本思路如下:
- 生成水印 :使用
canvas.toDataURL()
生成base64
水印图片数据,将水印图片数据赋值到水印div
上。 - 防止篡改
- 监控篡改 : 使用
MutationObserver.observe
监控水印元素、属性、内容、子元素变化,然后改变依赖数据flag.value++;
。 - 重新添加水印:定义响应式依赖数据
const flag = ref(0);
, 使用watchEffect
自动追踪依赖flag.value;
,重新添加水印元素
- 监控篡改 : 使用
代码
App.vue
<template>
<div class="container">
<WatermarkComponent text="少莫千华">
<div class="content" style="background-color: red;">
<img src="./assets/LA.png"/>
</div>
</WatermarkComponent>
<WatermarkComponent text="少莫千华">
<video controls class="video" src= './assets/LA.mp4'></video>
</WatermarkComponent>
</div>
</template>
<script>
import WatermarkComponent from './components/WatermarkComponent.vue'
export default {
name: 'App',
components:{
WatermarkComponent
}
}
</script>
<style scoped>
.container{
width: 100%;
display: flex;
justify-content: space-around;
position: relative;
}
.content{
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin: 3px;
}
img{
height: 100%;
width: 100%;
object-fit:cover;
}
video {
height: 110%;
width: 100%;
object-fit:fill;
}
.watermark-container {
position: relative;
flex-basis: 50%;
box-sizing: border-box;
}
video{
position: absolute;
flex-basis: 50%;
box-sizing: border-box;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
WatermarkComponent.vue
<template>
<div class="watermark-container" ref="parentRef">
<slot></slot>
<!-- 添加一个div,填充整个区域,设置水印背景,重复 -->
</div>
</template>
<script setup>
import { onMounted, onUnmounted,defineProps, ref, watchEffect } from 'vue';
import useWatermarkBg from './useWatermarkBg';
const props = defineProps( {
text: {
type: String,
required: true,
default: '少莫千华',
},
fontSize:{
type: Number,
default: 40,
},
gap:{
type:Number,
default:20,
},
});
const bg = useWatermarkBg(props);
const parentRef = ref(null);
// 定义一个依赖
const flag = ref(0);
let div;
//挂载以后添加水印
//监控元素变化、元素属性变化,防止篡改
//动态生成水印元素div
watchEffect(()=>{
flag.value;
if(!parentRef.value){
return ;
}
if(div)
{
div.remove();
}
const {base64,styleSize} = bg.value;
div = document.createElement('div');
div.style.backgroundImage = `url(${base64})`;
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
// 重复平铺
div.style.backgroundRepeat = 'repeat';
// 覆盖到同级的上一个元素
div.style.zIndex = 9999;
// 绝对定位
div.style.position = 'absolute';
// 设置边距
div.style.inset = 0;
//div.style.left = 0;
//div.style.right = 0;
//div.style.top = 0;
//div.style.bottom = 100;
//将水印添加到 .watermark-container 元素中
parentRef.value.appendChild(div);
});
onMounted(()=>{
//监控元素属性、子元素、内容、元素本身变化
let ob = new MutationObserver((records)=>{
console.log(records);
for(const record of records) {
// 判断删除的节点
for(const dom of record.removedNodes) {
// 判断节点是不是水印
if(dom === div) {
//删除水印元素触发 watchEffect
console.log('删除了水印元素');
// 修改依赖值,触发 watchEffect 重新运行
flag.value++;
return;
}
}
//修改水印元素属性触发 watchEffect
if(record.target === div){
console.log('修改了水印属性');
// 修改依赖值,触发 watchEffect 重新运行
flag.value++;
return;
}
//生产环境考虑到其他内容,完善,如 ZIndex 等等
}
});
// 监听 parentRef.value的变化
// 监听内容:childList、attributes、subtree
ob.observe(parentRef.value,{
childList: true,
attributes : true,
subtree: true,
});
onUnmounted(()=>{
ob && ob.disconnect();//取消监听
div = null;
});
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.wartermark-container{
position: relative;
}
</style>
生成水印图像 useWatermarkBg.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;
console.log(canvasSize + 'px');
canvas.width = canvasSize;
canvas.height = canvasSize;
ctx.translate(canvas.width/2, canvas.height/2);
//倾斜文本45°
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,
styleSize:canvasSize/devicePixelRatio,
};
});
}
资源
LA.png
LA.mp4
演讲开始素材