手把手教你做:高安全 Canvas 水印的实现与防篡改技巧

前端水印进阶:手把手教你打造高安全 Canvas 水印

🛡️ 1. 回顾与展望:从组件到自定义,安全升级!

在上篇博客中,我们一起领略了 Ant Design Vue 水印组件的魅力。它就像一个"傻瓜式"的智能安防系统,即插即用,省心省力,让我们的前端应用轻松拥有了水印功能。然而,我们也提到了它的"小娇气"------对框架的依赖性,以及在面对"专业级"破解者时,防篡改能力略显不足的短板。

那么,有没有一种方案,既能让我们完全掌控水印的每一个细节,又能拥有"金刚不坏之身",让那些试图篡改水印的"黑客"们无从下手呢?答案是肯定的!今天,我们就将揭开前端水印的"硬核"面纱,手把手教你如何利用 CanvasMutationObserver,打造一个高度自定义、防篡改能力超强的水印系统!

🎨 2. 方案二:自定义 Canvas 水印------硬核玩家的专属!

如果你是一个追求极致、喜欢掌控一切的"硬核玩家",那么自定义 Canvas 水印方案绝对是你的菜!它就像自己动手打造一套独一无二的指纹识别系统,虽然过程可能复杂一些,但最终的安全系数和定制化程度绝对能让你惊艳!

2.1 特点解读:自由、安全、无拘束!

这个方案的特点,简直是为那些对水印有特殊要求,或者对安全性有极高追求的项目量身定制的:

  • 完全自定义实现:从水印的形状、颜色、字体,到布局、透明度,甚至连水印的"灵魂"------绘制逻辑,你都可以一手掌控。想怎么画就怎么画,你的水印你做主!
  • 具备防篡改机制:这是这个方案最核心的亮点!通过巧妙的监听机制,一旦水印被"动了手脚",它就能像"不死鸟"一样,瞬间恢复原状,让那些试图篡改水印的人无功而返。
  • 支持复杂样式定制:想让水印动起来?想让水印根据用户行为变化?想让水印变成一个复杂的图案?只要你的 Canvas 功底足够,一切皆有可能!
  • 框架无关,通用性强:不像 Ant Design Vue 方案那样"挑食",这个方案不依赖任何特定的前端框架。无论是 Vue、React 还是 Angular,甚至是纯原生 JavaScript 项目,都能轻松集成,通用性极强。

2.2 核心实现:Canvas 魔法与 DOM 守护者!

自定义 Canvas 水印的核心在于两点:利用 Canvas 绘制水印图案,以及利用 MutationObserver 监听 DOM 变化,实现水印的防篡改。让我们一步步揭开它的神秘面纱。

2.2.1 Canvas 绘制水印图案

首先,我们需要创建一个 Canvas 元素,并在上面绘制我们想要的水印文字或图案。这个 Canvas 元素并不会直接显示在页面上,而是作为水印的"模板"。我们会将 Canvas 绘制的内容转换为图片,然后作为背景图应用到页面的一个 div 元素上。

arduino 复制代码
// utils/watermark.js
import tool from '@/utils/tool'
​
export const watermark = {
  set: function (text1, text2) {
    // 1. 创建 Canvas 元素
    const canvas = document.createElement('canvas')
    canvas.width = 150
    canvas.height = 120
    canvas.style.display = 'none' // 隐藏 Canvas 元素
    
    // 2. 获取 2D 绘图上下文
    const shuiyin = canvas.getContext('2d')
    
    // 3. 设置文字样式和位置
    shuiyin.rotate((-20 * Math.PI) / 180) // 逆时针旋转 20 度,让水印倾斜
    shuiyin.translate(-50, 20) // 调整文字的起始绘制位置,使其更居中或符合设计
    shuiyin.fillStyle = '#f5f5f5' // 设置文字颜色,这里是浅灰色
    shuiyin.font = '100 16px Microsoft JhengHei' // 设置字体样式和大小
    
    // 4. 绘制水印文字
    // 在 Canvas 上绘制两行文字,作为水印内容
    shuiyin.fillText(text1, canvas.width / 3, canvas.height / 2)
    shuiyin.fillText(text2, canvas.width / 3, canvas.height / 2 + 20)
    
    // 5. 创建水印容器
    const watermark = document.createElement('div')
    // 构建水印容器的样式字符串,使其覆盖整个视口,并设置为重复背景图
    const styleStr = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      z-index: 99999; // 确保水印在最上层
      pointer-events: none; // 允许鼠标事件穿透水印,不影响页面交互
      background-repeat: repeat; // 背景图重复平铺
      mix-blend-mode: multiply; // 混合模式,使水印与背景内容融合更自然
      background-image: url('${canvas.toDataURL('image/png')}') // 将 Canvas 内容转为 Data URL 作为背景图
    `
    
    watermark.setAttribute('style', styleStr) // 设置样式
    watermark.classList.add('watermark') // 添加 class,方便识别和操作
    document.body.appendChild(watermark) // 将水印容器添加到 body 中
    
    // ... (防篡改监听机制部分将在下一节详细讲解)
  },
  
  close: function () {
    // 移除水印
    let watermark = document.body.querySelector(".watermark")
    if (watermark) {
      document.body.removeChild(watermark)
    }
  }
}

代码解析

  • 创建 Canvas 并绘制 :我们创建了一个隐藏的 canvas 元素,通过 getContext('2d') 获取 2D 绘图上下文。然后,我们设置了水印的旋转角度 (rotate)、偏移量 (translate)、颜色 (fillStyle) 和字体 (font)。最后,使用 fillText 方法在 canvas 上绘制了两行文字。
  • 生成 Data URLcanvas.toDataURL('image/png') 是一个非常关键的方法,它将 canvas 上绘制的内容转换为一个 Base64 编码的 PNG 图片数据,这个数据可以直接作为 CSS 的 background-image 使用。
  • 创建水印容器 :我们创建了一个 div 元素作为水印的容器,并设置了一系列 CSS 样式,使其固定定位、覆盖整个视口、层级最高 (z-index: 99999),并且最重要的是 pointer-events: none,这样用户就无法通过鼠标点击到水印,不会影响页面的正常交互。background-repeat: repeat 确保水印图案在整个页面平铺。
  • 混合模式 mix-blend-mode: multiply:这是一个 CSS3 属性,它可以让水印与页面内容更好地融合,产生一种"印在纸上"的视觉效果,而不是简单地覆盖在上面。
2.2.2 防篡改机制详解:水印的"不死之身"!

光有水印还不够,如果用户可以轻易地通过开发者工具删除或修改水印,那它的防护作用就大打折扣了。这时候,MutationObserver 就该登场了!它就像一个忠诚的"DOM 守护者",时刻监控着页面的变化,一旦发现水印被"动了手脚",就会立即"修复"它。

dart 复制代码
// ... (接上一段代码)
​
    // 6. 防篡改监听机制
    const observer = new MutationObserver(() => {
      // 检查登录状态,如果用户已退出,则关闭水印并停止监听
      if (!tool.data.get('TOKEN')) {
        this.close()
        observer.disconnect()
        return
      }
      
      const wmInstance = document.body.querySelector('.watermark')
      
      // 检测水印是否被删除或修改
      if (!wmInstance || wmInstance.getAttribute('style') !== styleStr) {
        if (wmInstance) {
          // 样式被修改,重新设置
          wmInstance.setAttribute('style', styleStr)
        } else {
          // 元素被删除,重新添加
          if (tool.data.get('TOKEN')) {
            document.body.appendChild(watermark) // 重新添加水印元素
          } else {
            observer.disconnect() // 如果已退出登录,则停止监听
          }
        }
      }
    })
    
    // 7. 开始监听 DOM 变化
    // 监听 document.body 的属性变化、子树变化和子节点变化
    observer.observe(document.body, {
      attributes: true,    // 监听属性变化,例如 style 属性被修改
      subtree: true,      // 监听所有后代节点的变化
      childList: true     // 监听子节点的增删
    })
  },
  
  // ... (close 方法)
}

防篡改机制解析

  • MutationObserver :这是一个现代浏览器提供的 API,用于监听 DOM 树的变化。我们可以配置它监听元素的属性变化 (attributes)、子节点增删 (childList),以及整个子树的变化 (subtree)。

  • 监听回调函数 :当 MutationObserver 监听到 DOM 发生变化时,会触发我们定义的回调函数。在这个回调函数中,我们首先检查用户的登录状态,确保只有在登录状态下才维护水印。

  • 检测与修复

    • 水印元素被删除 :如果 document.body.querySelector('.watermark') 返回 null,说明水印元素被删除了,我们会判断用户是否登录,如果登录则重新将水印元素添加到 body 中。
    • 水印样式被修改 :如果水印元素存在,但其 style 属性与我们最初设置的 styleStr 不一致,说明水印的样式被修改了。我们会立即重新设置 style 属性,将其恢复原状。
  • 停止监听 :当用户退出登录时,我们会调用 observer.disconnect() 停止监听,避免不必要的性能开销。

通过这种机制,自定义 Canvas 水印方案能够有效地防止水印被删除或篡改,大大提升了水印的安全性。

2.3 使用方法:简单调用,轻松驾驭!

虽然实现过程比较复杂,但使用起来却非常简单。你只需要在需要显示水印的页面或组件中,导入 watermark 工具,然后调用 set 方法即可。需要移除水印时,调用 close 方法。

csharp 复制代码
// 导入水印工具
import { watermark } from '@/utils/watermark'
​
// 添加水印,传入两行文字内容
watermark.set('用户姓名', '用户账号')
​
// 移除水印
watermark.close()

2.4 优点分析:安全、灵活、无所不能!✅

  • 高度可定制,样式灵活:想怎么玩就怎么玩,你的水印你做主!
  • 防篡改能力强MutationObserver 就像一个忠诚的卫士,时刻守护着你的水印,让它"不死不灭"。
  • 框架无关,通用性好:无论你的项目用什么框架,都能轻松集成,没有任何"水土不服"的问题。
  • 支持复杂的水印效果:只要你有创意,Canvas 就能帮你实现!

2.5 缺点剖析:门槛较高,性能挑战!❌

  • 实现复杂,代码量大:相比 Ant Design Vue 组件方案,这个方案需要你对 Canvas 绘图和 DOM 监听有更深入的理解,代码量也相对较大。
  • 需要处理兼容性问题 :虽然 CanvasMutationObserver 在现代浏览器中支持良好,但在一些老旧浏览器中可能存在兼容性问题,需要额外处理。
  • 性能开销相对较大MutationObserver 会持续监听 DOM 变化,这会带来一定的性能开销。如果页面 DOM 结构非常复杂,或者变化非常频繁,可能会影响页面性能。因此,需要进行适当的优化,例如防抖处理。

总而言之,自定义 Canvas 水印方案是前端水印领域的"硬核武器",它赋予了你极致的控制权和强大的防篡改能力。如果你对水印的安全性有极高要求,或者需要实现一些独特的、复杂的视觉效果,那么这个方案绝对值得你投入时间和精力去探索和实践!

相关推荐
RoyLin2 小时前
图数据库基础
前端·后端·typescript
可乐爱宅着2 小时前
如何在next.js中处理表单提交
前端·next.js
卤代烃2 小时前
[性能优化] 如何高效的获取 base64Image 的 meta 信息
前端·性能优化·agent
IT_陈寒2 小时前
Vite 5大性能优化技巧:构建速度提升300%的实战分享!
前端·人工智能·后端
ssshooter2 小时前
WebGL 整个运行流程是怎样的?shader 是怎么从内存取到值?
前端·webgl
默默地离开2 小时前
小白学习react native 第一天
前端·react native
Mintopia2 小时前
在 Next.js 中开垦后端的第一块菜地:/pages/api 的 REST 接口
前端·javascript·next.js
无羡仙2 小时前
为什么await可以暂停函数的执行
前端·javascript
xw52 小时前
不定高元素动画实现方案(下)
前端·javascript·css