Vue 自定义水印指令实现方案解析

一、功能概述

这个 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')

五、性能优化建议

  1. 缓存水印图片:对相同配置的水印可缓存生成的 base64 数据

  2. 节流处理:在频繁更新的场景下添加防抖节流

  3. Web Worker:将 Canvas 计算放入 Worker 线程

  4. CSS 替代方案:考虑使用纯 CSS 的 repeating-linear-gradient 实现简单水印

六、适用场景

  1. 企业管理后台系统

  2. 在线文档预览功能

  3. 敏感数据展示页面

  4. 需要版权保护的内容页面

这个水印方案通过巧妙的 Canvas 生成和 DOM 监控,实现了既美观又安全的水印效果,是前端保护内容安全的有效解决方案。

相关推荐
EnCi Zheng10 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen14 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技15 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人26 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实26 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha37 分钟前
三目运算符
linux·服务器·前端
晓晨的博客44 分钟前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
不可能的是1 小时前
从 /simplify 指令深挖 Claude Code 多 Agent 协同机制
javascript