前端实现水印并防篡改

JavaScript性能优化实战 10w+人浏览 423人参与

我发现element-plus中的水印(Watermark)组件我在浏览器面板中去删除dom元素或者设置css属性将其隐藏掉,发现不可行!这是怎么实现的呢?原来是使用了MutationObserver这个API。我这使用了TailwindCss和Vue3、TS进行举例。

标签布局

//容器元素
<div class="h-full overflow-hidden" ref="watermarkRef"></div>

生成base64图片

<script lang="ts" setup>
interface WatermarkInfo {
  text: string,
  width?: number
  height?: number
}

const watermarkinfo = reactive({
  text: 'FCV',
})

const useWatermarkBg = (props: WatermarkInfo) => {
  const { text } = props
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  if (!ctx) return ''
  // 获取文字宽度
  const textMetrics = ctx.measureText(text)
  const fontWidth = textMetrics.width
  // 设置画布宽高
  canvas.width = 3 * fontWidth
  // 设置画布高度
  canvas.height = 3 * fontWidth
  // 设置字体大小
  const fontsize = canvas.width / (text.length + 2)
  // 设置字体
  ctx.font = `${fontsize}px "Microsoft YaHei", "PingFang SC", sans-serif`
  // 设置填充颜色
  ctx.fillStyle = `#3b82f6`
  // 设置文字水平居中
  ctx.textAlign = 'center'
  // 设置文字垂直居中
  ctx.textBaseline = 'middle'
  // 设置旋转角度
  ctx.rotate((10 * Math.PI) / 180)
  // 文字中心绘制
  ctx.fillText(text, canvas.width / 2, canvas.width / 2)
  // 转为base64
  return canvas.toDataURL()
}
</script>

重置水印元素

<script lang='ts' setup>

// 获取父元素
const watermarkRef = useTemplateRef('watermarkRef')

let div: HTMLDivElement
// 重置防止篡改
function resetWatermark() {
  if (!watermarkRef.value) return
  if (div) {
    // 删除之前的div
    div.remove()
  }
  // 获取base64
  const base64Url = useWatermarkBg(watermarkinfo)
  // 重新创建新的div并添加到watermarkRef元素中
  div = document.createElement('div')
  // 设置背景图片
  div.style.background = `url(${base64Url}) repeat 0 0`
  // 设置不可点击
  div.style.pointerEvents = 'none'
  div.style.width = '100%'
  div.style.height = '100%'
  // 添加到父元素
  watermarkRef.value.appendChild(div)
}
</script>

监听元素变化

当需要监测 DOM 的变化(如节点的增减、属性的修改、文本内容的变动等)时,MutationObserver是一个非常实用的 API。为开发者提供了一种异步观察 DOM 树结构变化的能力,能够高效地响应 DOM 的动态修改。

<script lang='ts' setup>
const ob = new MutationObserver((mutationsList) => {
  for (let mutation of mutationsList) {
    for (const node of mutation.removedNodes) {
      // 判断删除中的节点是否是添加水印的节点,是的话重新生成新的节点
      if (node === div) {
        resetWatermark()
        return
      }
    }
    // 当修改属性时,mutation.target就是修改的元素,所以需要判断
    if (mutation.target === div) {
      resetWatermark()
    }
  }
})

onMounted(() => {
  resetWatermark()
  ob.observe(watermarkRef.value!, {
    //监听元素属性变化
    attributes: true,
    //监听子元素变化
    childList: true,
    //监听子树变化
    subtree: true
  })
})

onUnmounted(() => {
  // 组件卸载停止监听
  ob.disconnect()
})
</script>

完整代码

<script lang='ts' setup>
interface WatermarkInfo {
  text: string,
  width?: number
  height?: number
}
const watermarkinfo = reactive({
  text: 'FCV',
})

// 获取父元素
const watermarkRef = useTemplateRef('watermarkRef')

const useWatermarkBg = (props: WatermarkInfo) => {
  const { text } = props
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  if (!ctx) return ''
  // 获取文字宽度
  const textMetrics = ctx.measureText(text)
  const fontWidth = textMetrics.width
  // 设置画布宽高
  canvas.width = 3 * fontWidth
  // 设置画布高度
  canvas.height = 3 * fontWidth
  // 设置字体大小
  const fontsize = canvas.width / (text.length + 2)
  // 设置字体
  ctx.font = `${fontsize}px "Microsoft YaHei", "PingFang SC", sans-serif`
  // 设置填充颜色
  ctx.fillStyle = `#3b82f6`
  // 设置文字水平居中
  ctx.textAlign = 'center'
  // 设置文字垂直居中
  ctx.textBaseline = 'middle'
  // 设置旋转角度
  ctx.rotate((10 * Math.PI) / 180)
  // 文字中心绘制
  ctx.fillText(text, canvas.width / 2, canvas.width / 2)
  // 转为base64
  return canvas.toDataURL()
}

let div: HTMLDivElement
// 重置防止篡改
function resetWatermark() {
  if (!watermarkRef.value || !watermarkinfo.text) return
  if (div) {
    // 删除之前的div
    div.remove()
  }
  // 获取base64
  const base64Url = useWatermarkBg(watermarkinfo)
  // 重新创建新的div并添加到watermarkRef元素中
  div = document.createElement('div')
  // 设置背景图片
  div.style.background = `url(${base64Url}) repeat 0 0`
  // 设置不可点击
  div.style.pointerEvents = 'none'
  div.style.width = '100%'
  div.style.height = '100%'
  // 添加到父元素
  watermarkRef.value.appendChild(div)
}

const ob = new MutationObserver((mutationsList) => {
  for (let mutation of mutationsList) {
    for (const node of mutation.removedNodes) {
      // 判断删除中的节点是否是添加水印的节点,是的话重新生成新的节点
      if (node === div) {
        resetWatermark()
        return
      }
    }
    // 当修改属性时,mutation.target就是修改的元素,所以需要判断
    if (mutation.target === div) {
      resetWatermark()
    }
  }
})

function reset() {
  watermarkinfo.text = 'FCV'
  resetWatermark()
}
function download() {
  const base64Url = useWatermarkBg(watermarkinfo)
  const a = document.createElement('a')
  a.href = base64Url
  a.download = 'watermark.webp'
  a.click()
}
onMounted(() => {
  resetWatermark()
  ob.observe(watermarkRef.value!, {
    //监听元素属性变化
    attributes: true,
    //监听子元素变化
    childList: true,
    //监听子树变化
    subtree: true
  })
})

onUnmounted(() => {
  // 组件卸载停止监听
  ob.disconnect()
})

</script>

<template>
  <div class="h-full">
    <div class="h-full overflow-hidden" ref="watermarkRef"></div>
  </div>
</template>

<style scoped lang='scss'></style>

欢迎评论区留言!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值