自定义指令--为指定元素添加前端水印(MutationObserver版)

181 阅读1分钟
总有老六能通过控制台右键水印元素并轻轻地点一下“Delete Element”便让前端水印不复存在肿么办?

基于Vue3实现的自定义指令 v-watermark

通过MutationObserver监听水印元素的操作,一旦水印元素被删除或者样式属性被更改,就重新执行一遍生成水印的逻辑

以下所有配置字段均为可选

字段名称字段值类型描述
fontstring水印字体大小、类型
textColorstring水印字体颜色
textstring水印文本
textWidthnumber水印文本宽度
textHeightnumber水印文本高度
textRotatenumber水印字体的旋转角度
canvasWidthnumber控制渲染的水印列数
canvasHeightnumber控制渲染的水印行数
<template>
  <header>
    <div class="wrapper" v-watermark="waterMarkInfo">
      <img src="./assets/logo.svg" class="wrapper-img" alt="vue-logo" />
    </div>
  </header>
  <RouterView />
</template>

<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。

<script setup lang="ts">
import { RouterView } from 'vue-router'
import type { Directive } from 'vue'

type TValue = {
  font?: string
  textColor?: string
  text?: string
  textWidth?: number
  textHeight?: number
  textRotate?: number
  canvasWidth?: number
  canvasHeight?: number
}

const waterMarkId = 'notTumbler-WaterMark'
const canvasId = 'notTumbler-Canvas'
// 水印相关设置
const waterMarkInfo = {
  font: '30px JhengHei',
  text: '水印',
  textColor: 'rgba(180, 180, 180, 0.4)',
  textWidth: 40,
  textHeight: 50,
  textRotate: -20,
  canvasWidth: 200,
  canvasHeight: 270,
}

const drawWatermark = (el: HTMLBaseElement, value: TValue) => {
  const { width: elWidth, height: elHeight } = el.getBoundingClientRect()
  const {
    font = '16px Microsoft JhengHei',
    textColor = 'rgba(180, 180, 180, 0.3)',
    text = 'notTumbler',
    textWidth = 40,
    textHeight = 50,
    canvasWidth = 200,
    canvasHeight = 240,
    textRotate = -20,
  } = value
  // 创建一个canvas标签
  const canvasTag = document.getElementById(canvasId) as HTMLCanvasElement
  const canvas = canvasTag || document.createElement('canvas')
  canvas.id = canvasId
  el.appendChild(canvas)
  // 设置宽高
  canvas.width = canvasWidth
  canvas.height = canvasHeight
  canvas.style.display = 'none'
  const ctx = canvas.getContext('2d')!
  // 画布相关设置
  ctx.rotate((textRotate * Math.PI) / 180)
  ctx.font = font
  ctx.fillStyle = textColor
  ctx.textAlign = 'left'
  ctx.textBaseline = 'middle'
  ctx.fillText(text, textWidth, textHeight)

  // 水印容器
  const waterMaskDiv = document.createElement('div')
  waterMaskDiv.id = waterMarkId
  // 设置容器的属性样式
  // 将刚刚生成的canvas内容转成图片,并赋值给容器的 background-image 样式
  const styleStr = `
    width: ${elWidth}px;
    height: ${elHeight}px;
    position: absolute;
    z-index: 9999999;
    top: 0;
    left: 0;
    pointer-events: none;
    background-image: url(${canvas.toDataURL('image/png')})
  `
  waterMaskDiv.setAttribute('style', styleStr)
  el.appendChild(waterMaskDiv)
  return styleStr
}

const vWatermark: Directive = {
  mounted(el, { value }) {
    // 将当前水印styleStr挂载到元素下,便于对比
    el.waterMarkStyleStr = drawWatermark(el, value)
    el.observer = new MutationObserver(() => {
      const instance = document.getElementById(waterMarkId)
      const style = instance?.getAttribute('style')
      const { waterMarkStyleStr } = el
      if (!instance) {
        drawWatermark(el, value)
      } else if (style !== waterMarkStyleStr) {
        instance.setAttribute('style', waterMarkStyleStr)
      }
    })
    el.observer.observe(document.body, {
      childList: true,
      attributes: true,
      subtree: true,
    })
  },
  unmounted(el) {
    el.observer.disconnect()
    el.observer = null
  },
}
</script>
<style scoped>
.wrapper {
  width: 600px;
  height: 600px;
  border: 2px solid rgb(202, 134, 8);
}
.wrapper-img {
  height: 100%;
  width: 100%;
}
</style>

我是老6

这样就能防住我吗?🤣🤣

image.png

姆猴移C,我反手就是一套禁用js再接Delete Element的组合拳,还是能去掉你的前端水印。 当然了,如果你直接让后端给图片或者文件加好水印后返回给前端渲染的话,emm...那我暂时是莫得办法😊。