当前位置: 首页 > news >正文

vue3 组件篇 WaterMark

文章目录

    • 组件介绍
    • canvas实现文本转图案
    • 插槽方式引入用户的内容
    • WaterMark.vue

组件介绍

Watermark(水印)‌ 是一种在页面上叠加半透明文字或图案的技术,常用于保护敏感信息(如文档、图片、网页)的版权或防止未经授权的传播。以下是其核心要点:

1‌. 信息标识‌:显示版权、用户信息或保密等级(如 “Confidential”)。
2‌. 防篡改‌:通过覆盖页面内容,增加数据泄露的难度。
‌3. 低干扰‌:半透明设计避免遮挡主体内容。

‌Canvas生成‌:用 Canvas 绘制文本/图案,转为 Base64 图片作为背景。
‌防删除‌:通过 MutationObserver 监听 DOM 改动,自动恢复被删除的水印。

canvas实现文本转图案

useWaterMarkBg.ts

import { computed } from 'vue'

interface WaterMarkProps {
    text: string,
    fontSize: number,
    gap: number,
    rotate?: number
    color?: string,
}
export default function useWaterMarkBg(props: WaterMarkProps) {
    return computed(() => {
        // 创建一个canvas 通过canvas生成图片
        const canvas = document.createElement('canvas')
        const ctx: any = canvas.getContext('2d')

        const devicePixelRatio = window.devicePixelRatio || 1
        const fontSize = props.fontSize * devicePixelRatio
        const font = fontSize + 'px serif'

        // 获取文字宽度
        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(canvasSize / 2, canvasSize / 2)
        ctx.rotate(props.rotate || -Math.PI / 4)
        ctx.textAlign = 'center'
        ctx.textBaseline = 'middle'
        ctx.fillStyle = props.color
        ctx.font = font
        ctx.fillStyle = props.color || '#ccc'
        ctx.fillText(props.text, 0, 0)

        return {
            base64: canvas.toDataURL(),
            size: canvasSize,
            styleSize: canvasSize / devicePixelRatio
        }

    })
}

插槽方式引入用户的内容

<template>
  <div ref="waterMarkContanier" style="position: relative; z-index: 1" :key="reRender">
    <!-- 内容容器的zindex被限制,始终低于水印的zindex,这样就无法通过更改插槽内容的样式,来覆盖水印 -->
    <div ref="contentContainer" style="position: relative; z-index: 1">
      <slot></slot>
    </div>
  </div>
</template>

WaterMark.vue

<template>
  <div ref="waterMarkContanier" style="position: relative; z-index: 1" :key="reRender">
    <!-- 内容容器的zindex被限制,始终低于水印的zindex,这样就无法通过更改插槽内容的样式,来覆盖水印 -->
    <div ref="contentContainer" style="position: relative; z-index: 1">
      <slot></slot>
    </div>
  </div>
</template>

<script lang="ts" name="waterMark" setup>
import useWaterMarkBg from '@/utrils/useWaterMarkBg'
import { watch, ref, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  text: {
    type: String,
    default: 'DX UI',
    required: true,
  },
  // 水印的字体大小
  fontSize: {
    type: Number,
    default: 20,
  },
  // 生成水印的间隙
  gap: {
    type: Number,
    default: 10,
  },
  // 生成水印的颜色
  color: {
    type: String,
    default: '#ccc',
  },
  // 水印的层级 默认是0 即水印在内容下方
  // 如果水印在内容下方,水印仍可能被有心者遮盖,
  // 想要更安全可将水印的层级调高zIndex设置为2以上,
  // 但这样会让水印在内容的上方
  zIndex: {
    type: Number,
    default: 0,
  },
})
// 通过waterMarkContanier监听dom变化,防止篡改
const waterMarkContanier = ref<HTMLDivElement | null>(null)
// 监听篡改的元素是否是内容元素,如果是就重新渲染组件
const contentContainer = ref<HTMLDivElement | null>(null)
// 水印背景数据
const waterMarkBg = useWaterMarkBg(props)
// 控制水印重新渲染 防止水印被篡改
const domChange = ref(0)
// 控制dom的key重新渲染,防止dom被篡改
const reRender = ref(0)
let div: HTMLDivElement | null = null

watch(
  () => [waterMarkContanier.value, domChange.value],
  () => {
    if (!waterMarkContanier.value) return
    // 防水印被篡改
    // 如果div存在,就移除
    if (div) {
      div.remove()
    }
    // 重新生成水印
    div = document.createElement('div')
    div.style.backgroundImage = `url(${waterMarkBg.value.base64})`
    div.style.position = 'absolute'
    div.style.inset = '0'
    div.style.zIndex = props.zIndex.toString()
    waterMarkContanier.value.appendChild(div)
  },
  {
    immediate: true,
  },
)

let observer: MutationObserver | null = null
onMounted(() => {
  if (!waterMarkContanier.value) return
  observer = new MutationObserver((record) => {
    for (let nodeList of record) {
      // 如果水印被修改了
      if (nodeList.target === div) {
        domChange.value++
        return
      }

      // 如果插槽内容元素被修改了,就通过修改key值,强制更新组件
      if (nodeList.target === contentContainer.value) {
        reRender.value++
        return
      }

      for (let node of nodeList.removedNodes) {
        // 如果水印被删除了
        if (node === div) {
          // 通过控制它触发watch 重新添加水印
          domChange.value++
          return
        }
      }
    }
  })
  observer.observe(waterMarkContanier.value, {
    attributes: true,
    childList: true,
    subtree: true,
    characterData: true,
  })
})

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

其中zIndex是关键,很多水印组件其实都没办法绝对控制水印始终正常显示,包括antd的水印组件也是一样。

如果传入的插槽内容有class,只需要给这个class加样式,加背景,加position,加zIndex 就能把水印给覆盖掉。

因为这些组件默认水印都在内容的下方,如果水印始终在内容的上方,通过给waterMark组件传入更高的zIndex改变层级,就能避免这个问题。

相关文章:

  • 23种设计模式-策略(Strategy)设计模式
  • 基于SpringBoot的“健身房管理系统”的设计与实现(源码+数据库+文档+PPT)
  • react ant design树穿梭框实现搜索并展开到子节点、同级节点选择及同时选择数量限制功能
  • 蓝桥杯备考-----》前缀和+哈希表之连续自然数和
  • javafx项目结构+代码规范
  • 西域平台商品详情接口开发指南(含代码示例)
  • Css布局-常规流笔记
  • LeetCode02.07.链表相交
  • AVI格式:经典视频格式的坚守与挑战
  • 在word中使用zotero添加参考文献并附带超链接
  • SpringBoot整合高德地图完成天气预报功能
  • 火山引擎云上实战:QwQ-32B 大模型快速部署
  • Python小练习 Vol.1:汉诺塔的移动(递归思维初体验)
  • 深度学习基础-----神经⽹络与深度学习((美)MichaelNielsen )
  • VMware三种网络模式对比总结
  • 基于大模型的肿瘤预测与全流程诊疗辅助系统技术文档
  • 初见Springboot
  • Lag-Llama时间序列模型简单实现数据预测
  • Elasticsearch7.X建模各属性文档
  • 【深度学习与实践】线性回归模型
  • 廊坊网站建设-纵横网络+网站/无屏蔽搜索引擎
  • 仓库管理系统数据库设计/武汉seo排名
  • 长春网站设计平台/100个商业经典案例
  • 做推广类门户网站怎么样/网上开店如何推广自己的网店
  • wordpress做视频站/长沙官网seo收费标准
  • ios软件开发培训班/seo优化方式包括