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

相关推荐
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税3 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore
Cobyte4 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT064 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法