页面水印sdk源码

直接把js和html保存到本地,在浏览器打开即可看到效果

sdk.js

javascript 复制代码
/**
 * 水印SDK - 用于在网页上添加水印效果
 * @version 2.0.0
 * @author S.M.D
 * 增强版 - 具备强化的防删除保护机制
 */

;(function (global) {
  'use strict'

  // 默认配置
  const DEFAULT_CONFIG = {
    text: '', // 水印文本(必填)
    fontSize: 16, // 字体大小
    fontColor: 'rgba(0, 0, 0, 0.15)', // 字体颜色
    rotate: -20, // 旋转角度(度)
    gap: [100, 100], // 水印间距 [x, y]
    offset: [0, 0], // 偏移量 [x, y]
  }

  // 水印实例存储
  let watermarkInstance = null

  // 防护相关变量
  let observer = null
  let intervalChecker = null
  let protectionEnabled = true
  let originalStyles = null
  let backupElements = []

  /**
   * 创建水印
   * @param {Object} options - 水印配置选项
   * @param {string} options.text - 水印文本(必填)
   * @param {number} [options.fontSize=16] - 字体大小
   * @param {string} [options.fontColor='rgba(0, 0, 0, 0.15)'] - 字体颜色
   * @param {number} [options.rotate=-20] - 旋转角度
   * @param {Array} [options.gap=[100, 100]] - 水印间距
   * @param {Array} [options.offset=[0, 0]] - 偏移量
   * @returns {Object} 水印实例对象
   */
  function createWatermark(options = {}) {
    // 验证必填参数
    if (!options.text || typeof options.text !== 'string') {
      throw new Error('水印文本(text)是必填参数,且必须是字符串类型')
    }

    // 合并配置
    const config = Object.assign({}, DEFAULT_CONFIG, options)

    // 验证和处理配置参数
    config.fontSize = Math.max(
      1,
      Number(config.fontSize) || DEFAULT_CONFIG.fontSize
    )
    config.rotate = Number(config.rotate) || DEFAULT_CONFIG.rotate

    // 处理gap参数
    if (Array.isArray(config.gap)) {
      config.gap = [
        Math.max(1, Number(config.gap[0]) || DEFAULT_CONFIG.gap[0]),
        Math.max(1, Number(config.gap[1]) || DEFAULT_CONFIG.gap[1]),
      ]
    } else {
      config.gap = DEFAULT_CONFIG.gap
    }

    // 处理offset参数
    if (Array.isArray(config.offset)) {
      config.offset = [
        Number(config.offset[0]) || DEFAULT_CONFIG.offset[0],
        Number(config.offset[1]) || DEFAULT_CONFIG.offset[1],
      ]
    } else {
      config.offset = DEFAULT_CONFIG.offset
    }

    // 移除已存在的水印
    removeWatermark()

    // 创建主水印元素
    const watermarkEl = createWatermarkElement(config)

    // 创建备用水印元素(多重保护)
    const backupEl1 = createWatermarkElement(config, 'backup-1')
    const backupEl2 = createWatermarkElement(config, 'backup-2')

    // 添加到页面
    document.body.appendChild(watermarkEl)
    document.body.appendChild(backupEl1)
    document.body.appendChild(backupEl2)

    // 存储备用元素
    backupElements = [backupEl1, backupEl2]

    // 保存原始样式
    originalStyles = watermarkEl.style.cssText

    // 创建水印实例
    watermarkInstance = {
      element: watermarkEl,
      backupElements: backupElements,
      config: config,
      remove: removeWatermark,
      update: function (newOptions) {
        return createWatermark(Object.assign({}, config, newOptions))
      },
    }

    // 启动多重防护机制
    startProtection()

    return watermarkInstance
  }

  /**
   * 创建水印DOM元素
   * @param {Object} config - 水印配置
   * @param {string} suffix - 元素后缀标识
   * @returns {HTMLElement} 水印元素
   */
  function createWatermarkElement(config, suffix = 'main') {
    // 创建canvas生成水印图案
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    // 设置字体
    ctx.font = `${config.fontSize}px Arial, sans-serif`
    ctx.fillStyle = config.fontColor
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    // 计算文本尺寸
    const textMetrics = ctx.measureText(config.text)
    const textWidth = textMetrics.width
    const textHeight = config.fontSize

    // 计算旋转后的尺寸
    const radians = (config.rotate * Math.PI) / 180
    const cos = Math.abs(Math.cos(radians))
    const sin = Math.abs(Math.sin(radians))

    const rotatedWidth = textWidth * cos + textHeight * sin
    const rotatedHeight = textWidth * sin + textHeight * cos

    // 设置canvas尺寸(包含间距),创建2x2错位排列
    const singleWidth = rotatedWidth + config.gap[0]
    const singleHeight = rotatedHeight + config.gap[1]
    const canvasWidth = Math.ceil(singleWidth * 2)
    const canvasHeight = Math.ceil(singleHeight * 2)

    canvas.width = canvasWidth
    canvas.height = canvasHeight

    // 重新设置字体(canvas尺寸改变会重置样式)
    ctx.font = `${config.fontSize}px Arial, sans-serif`
    ctx.fillStyle = config.fontColor
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    // 绘制四个错位排列的水印,实现真正的错位效果
    const positions = [
      { x: singleWidth * 0.5, y: singleHeight * 0.5 }, // 左上
      { x: singleWidth * 1.5, y: singleHeight * 0.3 }, // 右上(垂直错位)
      { x: singleWidth * 0.3, y: singleHeight * 1.5 }, // 左下(水平错位)
      { x: singleWidth * 1.3, y: singleHeight * 1.2 }, // 右下(双向错位)
    ]

    positions.forEach((pos) => {
      ctx.save()
      ctx.translate(pos.x, pos.y)
      ctx.rotate(radians)
      ctx.fillText(config.text, 0, 0)
      ctx.restore()
    })

    // 创建水印容器元素
    const watermarkEl = document.createElement('div')
    watermarkEl.setAttribute('data-watermark', 'true')
    watermarkEl.setAttribute('data-watermark-id', suffix)

    // 生成随机ID增加识别难度
    const randomId = 'wm_' + Math.random().toString(36).substr(2, 9)
    watermarkEl.id = randomId

    const cssText = `
            position: fixed !important;
            top: 0 !important;
            left: 0 !important;
            width: 100% !important;
            height: 100% !important;
            pointer-events: none !important;
            background-image: url(${canvas.toDataURL()}) !important;
            background-repeat: repeat !important;
            background-position: ${config.offset[0]}px ${
      config.offset[1]
    }px !important;
            z-index: ${suffix === 'main' ? 9999999 : 9999998} !important;
            user-select: none !important;
            -webkit-user-select: none !important;
            -moz-user-select: none !important;
            -ms-user-select: none !important;
            opacity: ${suffix === 'main' ? 1 : 0.01} !important;
        `

    watermarkEl.style.cssText = cssText

    return watermarkEl
  }

  /**
   * 启动多重防护机制
   */
  function startProtection() {
    if (!protectionEnabled || !watermarkInstance) {
      return
    }

    // 启动MutationObserver监听
    startMutationObserver()

    // 启动定时检查机制
    startIntervalChecker()

    // 启动样式监听
    startStyleProtection()
  }

  /**
   * 启动MutationObserver监听(增强版)
   */
  function startMutationObserver() {
    if (!window.MutationObserver || !watermarkInstance) {
      return
    }

    observer = new MutationObserver(function (mutations) {
      let needRestore = false

      mutations.forEach(function (mutation) {
        // 检查是否有节点被删除
        if (mutation.type === 'childList') {
          mutation.removedNodes.forEach(function (node) {
            if (isWatermarkElement(node)) {
              needRestore = true
            }
          })
        }

        // 检查水印元素的属性是否被修改
        if (
          mutation.type === 'attributes' &&
          isWatermarkElement(mutation.target)
        ) {
          // 检查关键属性是否被篡改
          const target = mutation.target
          if (
            mutation.attributeName === 'style' ||
            mutation.attributeName === 'class' ||
            mutation.attributeName === 'id'
          ) {
            needRestore = true
          }
        }
      })

      // 恢复水印
      if (needRestore && watermarkInstance && protectionEnabled) {
        const config = watermarkInstance.config
        setTimeout(() => {
          if (protectionEnabled) {
            createWatermark(config)
          }
        }, 0)
      }
    })

    // 监听整个文档,包括所有子树
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: [
        'style',
        'class',
        'id',
        'data-watermark',
        'data-watermark-id',
      ],
    })
  }

  /**
   * 启动定时检查机制
   */
  function startIntervalChecker() {
    if (intervalChecker) {
      clearInterval(intervalChecker)
    }

    intervalChecker = setInterval(() => {
      if (!protectionEnabled || !watermarkInstance) {
        return
      }

      const mainElement = watermarkInstance.element
      const backupElements = watermarkInstance.backupElements || []

      // 检查主元素是否存在且正常
      const mainExists = document.contains(mainElement)
      const mainStyleValid = mainExists && isStyleValid(mainElement)

      // 检查备用元素
      const backupValid = backupElements.some(
        (el) => document.contains(el) && isStyleValid(el)
      )

      // 如果主元素或备用元素被破坏,立即恢复
      if (!mainExists || !mainStyleValid || !backupValid) {
        const config = watermarkInstance.config
        createWatermark(config)
      }
    }, 500) // 每500ms检查一次
  }

  /**
   * 启动样式保护
   */
  function startStyleProtection() {
    if (!watermarkInstance) return

    // 定期检查样式完整性
    const styleChecker = setInterval(() => {
      if (!protectionEnabled || !watermarkInstance) {
        clearInterval(styleChecker)
        return
      }

      const element = watermarkInstance.element
      if (element && document.contains(element)) {
        // 检查关键样式是否被篡改
        const computedStyle = window.getComputedStyle(element)
        if (
          computedStyle.position !== 'fixed' ||
          computedStyle.zIndex < '9999990' ||
          computedStyle.pointerEvents !== 'none'
        ) {
          // 样式被篡改,恢复水印
          const config = watermarkInstance.config
          createWatermark(config)
        }
      }
    }, 1000) // 每1秒检查一次样式
  }

  /**
   * 检查是否为水印元素
   */
  function isWatermarkElement(node) {
    if (!node || node.nodeType !== 1) return false

    return (
      node === watermarkInstance?.element ||
      watermarkInstance?.backupElements?.includes(node) ||
      node.getAttribute('data-watermark') === 'true'
    )
  }

  /**
   * 检查元素样式是否有效
   */
  function isStyleValid(element) {
    if (!element || !document.contains(element)) return false

    const computedStyle = window.getComputedStyle(element)
    return (
      computedStyle.position === 'fixed' &&
      parseInt(computedStyle.zIndex) >= 9999990 &&
      computedStyle.pointerEvents === 'none' &&
      computedStyle.width === '100%' &&
      computedStyle.height === '100%'
    )
  }

  /**
   * 移除水印
   */
  function removeWatermark() {
    // 停用保护机制
    protectionEnabled = false

    if (watermarkInstance && watermarkInstance.element) {
      const element = watermarkInstance.element
      if (element.parentNode) {
        element.parentNode.removeChild(element)
      }
    }

    // 移除备用元素
    if (watermarkInstance && watermarkInstance.backupElements) {
      watermarkInstance.backupElements.forEach((el) => {
        if (el.parentNode) {
          el.parentNode.removeChild(el)
        }
      })
    }

    // 移除所有水印元素(防止重复)
    const existingWatermarks = document.querySelectorAll(
      '[data-watermark="true"]'
    )
    existingWatermarks.forEach((el) => {
      if (el.parentNode) {
        el.parentNode.removeChild(el)
      }
    })

    watermarkInstance = null
    backupElements = []

    // 停止监听
    if (observer) {
      observer.disconnect()
      observer = null
    }

    // 停止定时检查
    if (intervalChecker) {
      clearInterval(intervalChecker)
      intervalChecker = null
    }
  }

  // 将函数挂载到window对象
  global.createWatermark = createWatermark
  global.removeWatermark = removeWatermark

  // 兼容模块化环境
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = { createWatermark, removeWatermark }
  }

  // AMD支持
  if (typeof define === 'function' && define.amd) {
    define(function () {
      return { createWatermark, removeWatermark }
    })
  }
})(typeof window !== 'undefined' ? window : this)

demo.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>水印防护功能测试页面</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        margin: 20px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        min-height: 100vh;
      }

      .container {
        max-width: 800px;
        margin: 0 auto;
        background: white;
        padding: 30px;
        border-radius: 10px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
      }

      h1 {
        color: #333;
        text-align: center;
        margin-bottom: 30px;
      }

      .test-section {
        margin: 20px 0;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 5px;
        background: #f9f9f9;
      }

      .test-section h3 {
        color: #4caf50;
        margin-bottom: 15px;
      }

      button {
        background: #4caf50;
        color: white;
        border: none;
        padding: 10px 20px;
        margin: 5px;
        border-radius: 5px;
        cursor: pointer;
        font-size: 14px;
      }

      button:hover {
        background: #45a049;
      }

      button.danger {
        background: #f44336;
      }

      button.danger:hover {
        background: #da190b;
      }

      .log {
        background: #000;
        color: #0f0;
        padding: 10px;
        margin: 10px 0;
        border-radius: 5px;
        font-family: monospace;
        height: 200px;
        overflow-y: auto;
      }

      .status {
        padding: 10px;
        margin: 10px 0;
        border-radius: 5px;
        font-weight: bold;
      }

      .status.success {
        background: #d4edda;
        color: #155724;
        border: 1px solid #c3e6cb;
      }

      .status.error {
        background: #f8d7da;
        color: #721c24;
        border: 1px solid #f5c6cb;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>🛡️ 水印防护功能测试</h1>

      <div class="test-section">
        <h3>1. 基础功能测试</h3>
        <button onclick="createTestWatermark()">创建水印</button>
        <button onclick="removeTestWatermark()" class="danger">移除水印</button>
        <button onclick="checkWatermarkStatus()">检查水印状态</button>
      </div>

      <div class="test-section">
        <h3>2. 防删除测试</h3>
        <button onclick="testDeleteWatermark()" class="danger">
          尝试删除水印元素
        </button>
        <button onclick="testHideWatermark()" class="danger">
          尝试隐藏水印
        </button>
        <button onclick="testModifyStyle()" class="danger">
          尝试修改水印样式
        </button>
        <button onclick="testRemoveAttributes()" class="danger">
          尝试移除水印属性
        </button>
      </div>

      <div class="test-section">
        <h3>3. 高级攻击测试</h3>
        <button onclick="testMassDelete()" class="danger">
          批量删除所有水印
        </button>
        <button onclick="testStyleOverride()" class="danger">
          样式覆盖攻击
        </button>
        <button onclick="testZIndexAttack()" class="danger">
          z-index层级攻击
        </button>
        <button onclick="testOpacityAttack()" class="danger">透明度攻击</button>
      </div>

      <div class="status" id="status">等待测试...</div>

      <div class="log" id="log">控制台日志将显示在这里...</div>
    </div>

    <script src="watermark-sdk.js"></script>
    <script>
      let watermarkInstance = null
      let testCount = 0

      function log(message, type = 'info') {
        const logEl = document.getElementById('log')
        const timestamp = new Date().toLocaleTimeString()
        const logLine = `[${timestamp}] ${message}\n`
        logEl.textContent += logLine
        logEl.scrollTop = logEl.scrollHeight

        console.log(message)
      }

      function updateStatus(message, isSuccess = true) {
        const statusEl = document.getElementById('status')
        statusEl.textContent = message
        statusEl.className = 'status ' + (isSuccess ? 'success' : 'error')
      }

      function createTestWatermark() {
        try {
          watermarkInstance = createWatermark({
            text: '测试水印 - 防删除保护',
            fontSize: 18,
            fontColor: 'rgba(255, 0, 0, 0.2)',
            rotate: -30,
            gap: [120, 80],
          })
          log('✅ 水印创建成功')
          updateStatus('水印已创建,防护机制已启动', true)
        } catch (error) {
          log('❌ 水印创建失败: ' + error.message)
          updateStatus('水印创建失败', false)
        }
      }

      function removeTestWatermark() {
        if (watermarkInstance) {
          watermarkInstance.remove()
          watermarkInstance = null
          log('✅ 水印已正常移除')
          updateStatus('水印已移除', true)
        } else {
          log('⚠️ 没有找到水印实例')
          updateStatus('没有水印可移除', false)
        }
      }

      function checkWatermarkStatus() {
        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        log(`📊 当前页面水印元素数量: ${watermarks.length}`)

        watermarks.forEach((wm, index) => {
          const style = window.getComputedStyle(wm)
          log(
            `  水印${index + 1}: ID=${wm.id}, z-index=${
              style.zIndex
            }, opacity=${style.opacity}`
          )
        })

        updateStatus(
          `发现 ${watermarks.length} 个水印元素`,
          watermarks.length > 0
        )
      }

      function testDeleteWatermark() {
        testCount++
        log(`🔥 测试 ${testCount}: 尝试删除水印元素...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        const initialCount = watermarks.length

        if (initialCount === 0) {
          log('❌ 没有找到水印元素')
          updateStatus('测试失败:没有水印', false)
          return
        }

        // 删除第一个水印元素
        watermarks[0].remove()
        log(`🗑️ 已删除水印元素 ${watermarks[0].id}`)

        // 等待防护机制响应
        setTimeout(() => {
          const newWatermarks = document.querySelectorAll(
            '[data-watermark="true"]'
          )
          const recovered = newWatermarks.length >= initialCount

          log(
            `📈 防护结果: 初始${initialCount}个 -> 当前${newWatermarks.length}个`
          )

          if (recovered) {
            log('✅ 防护成功!水印已自动恢复')
            updateStatus('防删除保护生效', true)
          } else {
            log('❌ 防护失败!水印未能恢复')
            updateStatus('防删除保护失效', false)
          }
        }, 1000)
      }

      function testHideWatermark() {
        testCount++
        log(`🔥 测试 ${testCount}: 尝试隐藏水印...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        if (watermarks.length === 0) {
          log('❌ 没有找到水印元素')
          return
        }

        const target = watermarks[0]
        target.style.display = 'none'
        log(`👻 已设置水印 ${target.id} 为 display: none`)

        setTimeout(() => {
          const style = window.getComputedStyle(target)
          const isVisible = style.display !== 'none'

          if (isVisible) {
            log('✅ 防护成功!水印显示已恢复')
            updateStatus('防隐藏保护生效', true)
          } else {
            log('❌ 防护失败!水印仍然隐藏')
            updateStatus('防隐藏保护失效', false)
          }
        }, 1000)
      }

      function testModifyStyle() {
        testCount++
        log(`🔥 测试 ${testCount}: 尝试修改水印样式...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        if (watermarks.length === 0) {
          log('❌ 没有找到水印元素')
          return
        }

        const target = watermarks[0]
        const originalZIndex = target.style.zIndex

        target.style.zIndex = '1'
        target.style.opacity = '0'
        log(`🎨 已修改水印样式: z-index=1, opacity=0`)

        setTimeout(() => {
          const style = window.getComputedStyle(target)
          const zIndexRestored = parseInt(style.zIndex) > 1000000
          const opacityRestored = parseFloat(style.opacity) > 0

          if (zIndexRestored && opacityRestored) {
            log('✅ 防护成功!样式已恢复')
            updateStatus('防样式篡改保护生效', true)
          } else {
            log(
              `❌ 防护失败!z-index=${style.zIndex}, opacity=${style.opacity}`
            )
            updateStatus('防样式篡改保护失效', false)
          }
        }, 1500)
      }

      function testRemoveAttributes() {
        testCount++
        log(`🔥 测试 ${testCount}: 尝试移除水印属性...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        if (watermarks.length === 0) {
          log('❌ 没有找到水印元素')
          return
        }

        const target = watermarks[0]
        target.removeAttribute('data-watermark')
        target.removeAttribute('data-watermark-id')
        log(`🏷️ 已移除水印属性`)

        setTimeout(() => {
          const hasAttribute = target.hasAttribute('data-watermark')

          if (hasAttribute) {
            log('✅ 防护成功!属性已恢复')
            updateStatus('防属性移除保护生效', true)
          } else {
            log('❌ 防护失败!属性未恢复')
            updateStatus('防属性移除保护失效', false)
          }
        }, 1000)
      }

      function testMassDelete() {
        testCount++
        log(`🔥 测试 ${testCount}: 批量删除攻击测试...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        const initialCount = watermarks.length

        if (initialCount === 0) {
          log('❌ 没有找到水印元素')
          return
        }

        // 删除所有水印
        watermarks.forEach((wm, index) => {
          wm.remove()
          log(`🗑️ 删除水印 ${index + 1}/${initialCount}`)
        })

        setTimeout(() => {
          const newWatermarks = document.querySelectorAll(
            '[data-watermark="true"]'
          )
          const recovered = newWatermarks.length > 0

          log(
            `📈 批量删除结果: 初始${initialCount}个 -> 当前${newWatermarks.length}个`
          )

          if (recovered) {
            log('✅ 防护成功!水印已重新创建')
            updateStatus('批量删除防护生效', true)
          } else {
            log('❌ 防护失败!所有水印被删除')
            updateStatus('批量删除防护失效', false)
          }
        }, 1500)
      }

      function testStyleOverride() {
        testCount++
        log(`🔥 测试 ${testCount}: 样式覆盖攻击测试...`)

        // 创建覆盖样式
        const style = document.createElement('style')
        style.textContent = `
                [data-watermark="true"] {
                    display: none !important;
                    opacity: 0 !important;
                    z-index: -1 !important;
                }
            `
        document.head.appendChild(style)
        log(`💉 已注入覆盖样式`)

        setTimeout(() => {
          const watermarks = document.querySelectorAll(
            '[data-watermark="true"]'
          )
          let visibleCount = 0

          watermarks.forEach((wm) => {
            const computedStyle = window.getComputedStyle(wm)
            if (
              computedStyle.display !== 'none' &&
              parseFloat(computedStyle.opacity) > 0 &&
              parseInt(computedStyle.zIndex) > 0
            ) {
              visibleCount++
            }
          })

          if (visibleCount > 0) {
            log('✅ 防护成功!样式覆盖被阻止')
            updateStatus('防样式覆盖保护生效', true)
          } else {
            log('❌ 防护失败!样式被成功覆盖')
            updateStatus('防样式覆盖保护失效', false)
          }

          // 清理测试样式
          document.head.removeChild(style)
        }, 2000)
      }

      function testZIndexAttack() {
        testCount++
        log(`🔥 测试 ${testCount}: z-index层级攻击测试...`)

        // 创建高层级遮挡元素
        const blocker = document.createElement('div')
        blocker.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: white;
                z-index: 99999999;
                pointer-events: none;
            `
        document.body.appendChild(blocker)
        log(`🚧 已创建z-index=99999999的遮挡层`)

        setTimeout(() => {
          const watermarks = document.querySelectorAll(
            '[data-watermark="true"]'
          )
          let higherZIndexCount = 0

          watermarks.forEach((wm) => {
            const zIndex = parseInt(window.getComputedStyle(wm).zIndex)
            if (zIndex > 99999999) {
              higherZIndexCount++
            }
          })

          if (higherZIndexCount > 0) {
            log('✅ 防护成功!水印z-index已提升')
            updateStatus('防z-index攻击保护生效', true)
          } else {
            log('❌ 防护失败!水印被遮挡')
            updateStatus('防z-index攻击保护失效', false)
          }

          // 清理测试元素
          document.body.removeChild(blocker)
        }, 1500)
      }

      function testOpacityAttack() {
        testCount++
        log(`🔥 测试 ${testCount}: 透明度攻击测试...`)

        const watermarks = document.querySelectorAll('[data-watermark="true"]')
        if (watermarks.length === 0) {
          log('❌ 没有找到水印元素')
          return
        }

        // 设置所有水印为完全透明
        watermarks.forEach((wm, index) => {
          wm.style.opacity = '0'
          log(`👻 设置水印${index + 1}透明度为0`)
        })

        setTimeout(() => {
          let visibleCount = 0
          watermarks.forEach((wm) => {
            const opacity = parseFloat(window.getComputedStyle(wm).opacity)
            if (opacity > 0) {
              visibleCount++
            }
          })

          if (visibleCount > 0) {
            log('✅ 防护成功!透明度已恢复')
            updateStatus('防透明度攻击保护生效', true)
          } else {
            log('❌ 防护失败!水印仍然透明')
            updateStatus('防透明度攻击保护失效', false)
          }
        }, 1500)
      }

      // 页面加载完成后自动创建测试水印
      window.addEventListener('load', () => {
        log('🚀 页面加载完成,开始测试...')
        setTimeout(createTestWatermark, 500)
      })
    </script>
  </body>
</html>
相关推荐
d***9351 天前
springboot3.X 无法解析parameter参数问题
android·前端·后端
q***71011 天前
Spring Boot(快速上手)
java·spring boot·后端
n***84071 天前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
better_liang1 天前
每日Java面试场景题知识点之-分布式事务处理
java·微服务·面试·springcloud·分布式事务
L***d6701 天前
Spring Boot 各种事务操作实战(自动回滚、手动回滚、部分回滚)
java·数据库·spring boot
凌波粒1 天前
Springboot基础教程(3)--自动装配原理/静态资源处理/欢迎页
java·spring boot·后端
likuolei1 天前
XSL-FO 软件
java·开发语言·前端·数据库
凌波粒1 天前
SpringBoot基础教程(2)--yaml/配置文件注入/数据校验/多环境配置
java·spring boot·后端·spring
正一品程序员1 天前
vue项目引入GoogleMap API进行网格区域圈选
前端·javascript·vue.js
S***26751 天前
Spring Boot环境配置
java·spring boot·后端