Skip to content

前端水印

调用水印页面:

<template>
  <Watermark text="版权所有">
    <div>啊哈哈哈哈哈哈哈哈</div>
  </Watermark>
</template>

<script setup>
import Watermark from '@/components/Watermark.vue'
</script>

水印组件:

<template>
  <div class="watermark-container" ref="parentRef">
    <slot></slot>
  </div>
</template>

<script setup>
import { ref, watchEffect, onMounted, onUnmounted } from 'vue'

import useWatermarkBg from '../utils/useWatermarkBg'

const props = defineProps({
  text: {
    // 传入水印的文本
    type: String,
    required: true,
    default: 'watermark'
  },
  fontSize: {
    // 字体的大小
    type: Number,
    default: 40
  },
  gap: {
    // 水印重复的间隔
    type: Number,
    default: 20
  }
})
const bg = useWatermarkBg(props)
const parentRef = ref(null)
const flag = ref(0) // 声明一个依赖
let div
watchEffect(() => {
  flag.value // 将依赖放在 watchEffect 里
  if (!parentRef.value) {
    return
  }
  if (div) {
    div.remove()
  }
  const { base64, styleSize } = bg.value
  div = document.createElement('div')
  div.style.backgroundImage = `url(${base64})`
  div.style.backgroundSize = `${styleSize}px ${styleSize}px`
  div.style.backgroundRepeat = 'repeat'
  div.style.zIndex = 9999
  div.style.position = 'absolute'
  div.style.inset = 0
  parentRef.value.appendChild(div)
})
let ob
onMounted(() => {
  ob = new MutationObserver((records) => {
    for (const record of records) {
      for (const dom of record.removedNodes) {
        if (dom === div) {
          flag.value++ // 删除节点的时候更新依赖
          return
        }
      }
      if (record.target === div) {
        flag.value++ // 修改属性的时候更新依赖
        return
      }
    }
  })
  ob.observe(parentRef.value, {
    childList: true,
    attributes: true,
    subtree: true
  })
})

onUnmounted(() => {
  ob && ob.disconnect()
  div = null
})
</script>

useWatermarkBg.js:

import { computed } from 'vue'

export default function useWatermarkBg(props) {
  return computed(() => {
    // 创建一个 canvas
    const canvas = document.createElement('canvas')
    const devicePixelRatio = window.devicePixelRatio || 1
    // 设置字体大小
    const fontSize = props.fontSize * devicePixelRatio
    const font = fontSize + 'px'
    const ctx = canvas.getContext('2d')
    // 获取文字宽度
    ctx.font = font
    const { width } = ctx.measureText(props.text)
    const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio
    canvas.width = canvasSize
    canvas.height = canvasSize
    ctx.translate(canvas.width / 2, canvas.height / 2)
    // 旋转 45 度让文字变倾斜
    ctx.rotate((Math.PI / 180) * -45)
    ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
    ctx.font = font
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    // 将文字画出来
    ctx.fillText(props.text, 0, 0)
    return {
      base64: canvas.toDataURL(),
      size: canvasSize,
      styleSize: canvasSize / devicePixelRatio
    }
  })
}