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 监控,实现了既美观又安全的水印效果,是前端保护内容安全的有效解决方案。

相关推荐
qczg_wxg10 小时前
React Native的动画系统
javascript·react native·react.js
漂流瓶jz11 小时前
解锁Babel核心功能:从转义语法到插件开发
前端·javascript·typescript
周小码11 小时前
shadcn-table:构建高性能服务端表格的终极解决方案 | 2025最新实践
前端·react.js
大怪v11 小时前
老乡,别走!Javascript隐藏功能你知道吗?
前端·javascript·代码规范
ERP老兵-冷溪虎山12 小时前
Python/JS/Go/Java同步学习(第三篇)四语言“切片“对照表: 财务“小南“纸切片术切凭证到崩溃(附源码/截图/参数表/避坑指南/老板沉默术)
java·javascript·python·golang·中医编程·四语言同步学习·职场生存指南
webYin12 小时前
vue2 打包生成的js文件过大优化
前端·vue.js·webpack
gnip12 小时前
结合Worker通知应用更新
前端·javascript
叶玳言12 小时前
【LVGL】从HTML到LVGL:嵌入式UI的设计迁移与落地实践
前端·ui·html·移植
高级测试工程师欧阳12 小时前
HTML 基本结构
前端
Gazer_S12 小时前
【Element Plus 表单组件样式统一 & CSS 文字特效实现指南】
前端·css·vue.js