效果
水印的作用
图片加水印的操作一般是由后端来完成,有些站点保护的知识产权的类型可能比较多,不仅仅是图片,可能还有视频、文字等等,对于不同类型的对象添加水印后端操作比较复杂,所有有些站点逐步的让前端去进行水印添加的操作。
前端框架
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) {//删除水印元素触发 watchEffectconsole.log('删除了水印元素');// 修改依赖值,触发 watchEffect 重新运行flag.value++;return;}}//修改水印元素属性触发 watchEffectif(record.target === div){console.log('修改了水印属性');// 修改依赖值,触发 watchEffect 重新运行flag.value++;return;}//生产环境考虑到其他内容,完善,如 ZIndex 等等}});// 监听 parentRef.value的变化// 监听内容:childList、attributes、subtreeob.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
演讲开始素材