一、功能概述
这个 Vue 自定义指令实现了在任意元素上添加防篡改水印的功能,主要特点包括:
-
可自定义文本内容、样式和布局
-
自动生成重复平铺的水印背景
-
提供防篡改保护机制
-
支持响应式更新
二、核心实现解析
1. 水印画布生成
javascript
function generateWatermark(config) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 计算文本尺寸并确定画布大小
ctx.font = config.font
const textWidth = ctx.measureText(config.text).width
const size = Math.max(textWidth, config.rowSpacing, config.colSpacing) * 1.5
canvas.width = canvas.height = size
// 绘制旋转文本
ctx.save()
ctx.font = config.font
ctx.fillStyle = config.color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.translate(size / 2, size / 2)
ctx.rotate((Math.PI / 180) * config.angle)
ctx.fillText(config.text, 0, 0)
ctx.restore()
return canvas.toDataURL('image/png')
}
技术要点:
-
动态计算画布尺寸确保水印间距
-
使用 Canvas 2D API 绘制旋转文本
-
输出 base64 格式的 PNG 图片数据
2. 水印元素创建
javascript
function createWatermarkElement(url, config) {
const watermark = document.createElement('div')
Object.assign(watermark.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: 'none',
backgroundImage: `url(${url})`,
backgroundRepeat: 'repeat',
zIndex: '9999',
opacity: config.opacity || '1'
})
return watermark
}
设计考虑:
-
使用绝对定位覆盖目标元素
-
pointer-events: none
确保不影响交互 -
平铺背景实现全屏水印效果
3. 防篡改保护机制
javascript
function observeWatermark(watermark, target) {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// 检测水印元素被移除
if (mutation.removedNodes.length) {
[...mutation.removedNodes].some(node =>
node === watermark && target.appendChild(watermark))
}
// 检测样式修改
if (mutation.attributeName === 'style') {
watermark.setAttribute('style', watermark.style.cssText)
}
}
})
observer.observe(target, {
attributes: true,
subtree: true,
childList: true,
attributeFilter: ['style']
})
return observer
}
安全策略:
-
使用 MutationObserver 监控 DOM 变化
-
自动恢复被删除的水印元素
-
锁定水印元素样式防止修改
三、Vue 指令集成
javascript
export default {
mounted(el, binding) {
nextTick(() => {
const config = { ...defaultConfig, ...binding.value }
const url = generateWatermark(config)
const watermark = createWatermarkElement(url, config)
el.style.position = 'relative'
el.appendChild(watermark)
if (config.observe) {
el._watermarkObserver = observeWatermark(watermark, el)
}
el._watermarkElement = watermark
})
},
unmounted(el) {
el._watermarkObserver?.disconnect()
el._watermarkElement?.remove()
}
}
最佳实践:
-
使用
nextTick
确保 DOM 就绪 -
合并默认配置和用户配置
-
妥善清理资源防止内存泄漏
四、使用示例
javascript
<template>
<div v-watermark="watermarkConfig" class="content-box">
<!-- 页面内容 -->
</div>
</template>
<script>
export default {
data() {
return {
watermarkConfig: {
text: '机密文档',
color: 'rgba(100, 130, 200, 0.1)',
angle: -15
}
}
}
}
</script>
完整代码
javascript
import { nextTick } from 'vue'
// 默认配置
const defaultConfig = {
text: '机密文件', // 水印文本
font: '16px Microsoft YaHei', // 字体
color: 'rgba(128, 128, 128, 0.2)', // 文字颜色
angle: -20, // 旋转角度
rowSpacing: 100, // 行间距
colSpacing: 100, // 列间距
observe: true // 是否开启防篡改保护
}
// 生成水印画布
function generateWatermark(config) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 计算文本宽度
ctx.font = config.font
const textWidth = ctx.measureText(config.text).width
// 设置画布大小
const size = Math.max(textWidth, config.rowSpacing, config.colSpacing) * 1.5
canvas.width = size
canvas.height = size
// 绘制水印
ctx.font = config.font
ctx.fillStyle = config.color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.translate(size / 2, size / 2)
ctx.rotate((Math.PI / 180) * config.angle)
ctx.fillText(config.text, 0, 0)
return canvas.toDataURL('image/png')
}
// 创建水印元素
function createWatermarkElement(url, config) {
const watermark = document.createElement('div')
watermark.style.position = 'absolute'
watermark.style.top = '0'
watermark.style.left = '0'
watermark.style.width = '100%'
watermark.style.height = '100%'
watermark.style.pointerEvents = 'none'
watermark.style.backgroundImage = `url(${url})`
watermark.style.backgroundRepeat = 'repeat'
watermark.style.zIndex = '9999'
watermark.style.opacity = config.opacity || '1'
return watermark
}
// 防篡改保护
function observeWatermark(watermark, target) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.removedNodes.length) {
mutation.removedNodes.forEach((node) => {
if (node === watermark) {
target.appendChild(watermark)
}
})
}
if (mutation.attributeName === 'style') {
watermark.setAttribute('style', watermark.style.cssText)
}
})
})
observer.observe(target, {
attributes: true,
subtree: true,
childList: true,
attributeFilter: ['style']
})
return observer
}
export default {
mounted(el, binding) {
nextTick(() => {
const config = { ...defaultConfig, ...binding.value }
const url = generateWatermark(config)
const watermark = createWatermarkElement(url, config)
el.style.position = 'relative'
el.appendChild(watermark)
if (config.observe) {
const observer = observeWatermark(watermark, el)
el._watermarkObserver = observer
}
el._watermarkElement = watermark
})
},
unmounted(el) {
if (el._watermarkObserver) {
el._watermarkObserver.disconnect()
}
if (el._watermarkElement) {
el.removeChild(el._watermarkElement)
}
}
}
全局注册
javascript
import { createApp } from 'vue'
import App from './App.vue'
import watermark from './directives/watermark'
const app = createApp(App)
app.directive('watermark', watermark)
app.mount('#app')
五、性能优化建议
-
缓存水印图片:对相同配置的水印可缓存生成的 base64 数据
-
节流处理:在频繁更新的场景下添加防抖节流
-
Web Worker:将 Canvas 计算放入 Worker 线程
-
CSS 替代方案:考虑使用纯 CSS 的 repeating-linear-gradient 实现简单水印
六、适用场景
-
企业管理后台系统
-
在线文档预览功能
-
敏感数据展示页面
-
需要版权保护的内容页面
这个水印方案通过巧妙的 Canvas 生成和 DOM 监控,实现了既美观又安全的水印效果,是前端保护内容安全的有效解决方案。