vue 水印组件

news2024/11/16 18:44:43

效果图展示![在这里插入图片描述](https://img-blog.csdnimg.cn/8ef5d498a87f432290ac61ca31cbc303.png

Watermark

参数说明类型默认值版本
width水印的宽度,content 的默认值为自身的宽度number120
height水印的高度,content 的默认值为自身的高度number64
rotate水印绘制时,旋转的角度,单位 °number-22
zIndex追加的水印元素的 z-indexnumber9
image图片源,建议导出 2 倍或 3 倍图,优先级高string-
content水印文字内容string | string[]-
font文字样式FontFont
gap水印之间的间距[number, number][100, 100]
offset水印距离容器左上角的偏移量,默认为 gap/2[number, number][gap[0]/2, gap[1]/2]

Font

参数说明类型默认值版本
color字体颜色stringrgba(0,0,0,.15)
fontSize字体大小number16
fontWeight字体粗细normal | light | weight | numbernormal
fontFamily字体类型stringsans-serif
fontStyle字体样式none | normal | italic | obliquenormal

使用Watermark 组件

    <Watermark :content="['watermark', 'Happy Working']" :width="130" :gap="[50, 50]">
      <div style="width: 500px; height: 500px; border: 1px"></div>
    </Watermark>

Watermark 组件

<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2023-05-09 
 -->

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

<script>
let watermarkRef = null
const BaseSize = 2
const FontGap = 3
const getPixelRatio = () => window.devicePixelRatio || 1
const toLowercaseSeparator = (key) => key.replace(/([A-Z])/g, '-$1').toLowerCase()
const getStyleStr = (style) =>
  Object.keys(style)
    .map((key) => `${toLowercaseSeparator(key)}: ${style[key]};`)
    .join(' ')

function reRendering(mutation, watermarkElement) {
  let flag = false
  // 是否删除水印节点
  if (mutation.removedNodes.length) {
    flag = Array.from(mutation.removedNodes).some((node) => node === watermarkElement)
  }
  // 是否修改过水印dom属性值
  if (mutation.type === 'attributes' && mutation.target === watermarkElement) {
    flag = true
  }

  return flag
}
export default {
  name: 'Watermark',
  data() {
    return {
      stopObservation: false,
      observe: null,
    }
  },
  props: {
    zIndex: { type: Number, default: 9 },
    rotate: { type: Number, default: -22 },
    width: { type: [String, Number], default: 120 },
    height: { type: [String, Number], default: 64 },
    image: { type: String, default: '' },
    content: { type: [String, Array], default: '' },
    font: {
      type: Object,
      default: () => ({
        fontSize: 16,
        fontFamily: 'sans-serif',
        fontStyle: 'normal',
        fontWeight: 'normal',
        color: 'rgba(0, 0, 0, 0.15)',
      }),
    },
    rootClassName: '',
    gap: { type: Array, default: () => [100, 100] },
    offset: { type: Array, default: () => [50, 50] },
  },

  mounted() {
    function onMutate(records) {
      if (this.stopObservation) return

      records.forEach((mutation) => {
        if (!reRendering(mutation, watermarkRef)) return
        this.destroyWatermark()
        this.renderWatermark()
      })
    }
    this.renderWatermark()
    this.observe = this.useMutationObserver(this.$el, onMutate.bind(this), { attributes: true, childList: true, subtree: true })
  },

  methods: {
    useMutationObserver(target, callback, options) {
      const isSupported = typeof MutationObserver !== 'undefined'
      if (!isSupported) return false
      const observe = new MutationObserver(callback)
      observe.observe(target, options)
      return observe
    },
    getMarkSize(ctx) {
      const props = this.$props
      const { fontSize, fontFamily } = props.font

      let defaultWidth
      let defaultHeight
      const content = props.content
      const image = props.image
      const width = props.width
      const height = props.height

      if (!image && ctx.measureText) {
        ctx.font = `${Number(fontSize)}px ${fontFamily}`
        const contents = Array.isArray(content) ? content : [content]
        const widths = contents.map((item) => ctx.measureText(item).width)
        defaultWidth = Math.ceil(Math.max(...widths))
        defaultHeight = Number(fontSize.value) * contents.length + (contents.length - 1) * FontGap
      }

      return [width ?? defaultWidth, height ?? defaultHeight]
    },
    rotateWatermark(ctx, rotateX, rotateY, rotate) {
      ctx.translate(rotateX, rotateY)
      ctx.rotate((Math.PI / 180) * Number(rotate))
      ctx.translate(-rotateX, -rotateY)
    },
    fillTexts(ctx, drawX, drawY, drawWidth, drawHeight) {
      const props = this.$props
      const { fontSize, fontFamily, fontStyle, fontWeight, color } = props.font

      const ratio = getPixelRatio()
      const content = props.content
      const mergedFontSize = Number(fontSize) * ratio
      ctx.font = `${fontStyle} normal ${fontWeight} ${mergedFontSize}px/${drawHeight}px ${fontFamily}`
      ctx.fillStyle = color
      ctx.textAlign = 'center'
      ctx.textBaseline = 'top'
      ctx.translate(drawWidth / 2, 0)
      const contents = Array.isArray(content) ? content : [content]
      contents?.forEach((item, index) => {
        ctx.fillText(item ?? '', drawX, drawY + index * (mergedFontSize + FontGap * ratio))
      })
    },
    appendWatermark(base64Url, markWidth) {
      if (!watermarkRef) return
      const props = this.$props
      const [gapX, gapY] = props.gap

      this.stopObservation = true
      const attrs = getStyleStr({ ...this.markStyle, backgroundImage: `url('${base64Url}')`, backgroundSize: `${(gapX + markWidth) * BaseSize}px` })
      watermarkRef.setAttribute('style', attrs)
      this.$el.append(watermarkRef)
      // Delayed execution
      setTimeout(() => {
        this.stopObservation = false
      })
    },
    renderWatermark() {
      const props = this.$props
      const [gapX, gapY] = props.gap

      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const image = props.image
      const rotate = props.rotate

      if (!ctx) return false
      if (!watermarkRef) {
        watermarkRef = document.createElement('div')
      }

      const ratio = getPixelRatio()
      const [markWidth, markHeight] = this.getMarkSize(ctx)
      const canvasWidth = (gapX + markWidth) * ratio
      const canvasHeight = (gapY + markHeight) * ratio
      canvas.setAttribute('width', `${canvasWidth * BaseSize}px`)
      canvas.setAttribute('height', `${canvasHeight * BaseSize}px`)

      const drawX = (gapX * ratio) / 2
      const drawY = (gapY * ratio) / 2
      const drawWidth = markWidth * ratio
      const drawHeight = markHeight * ratio
      const rotateX = (drawWidth + gapX * ratio) / 2
      const rotateY = (drawHeight + gapY * ratio) / 2
      /** Alternate drawing parameters */
      const alternateDrawX = drawX + canvasWidth
      const alternateDrawY = drawY + canvasHeight
      const alternateRotateX = rotateX + canvasWidth
      const alternateRotateY = rotateY + canvasHeight

      ctx.save()
      this.rotateWatermark(ctx, rotateX, rotateY, rotate)

      if (image) {
        const img = new Image()
        img.onload = () => {
          ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight)
          /** Draw interleaved pictures after rotation */
          ctx.restore()
          this.rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
          ctx.drawImage(img, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
          this.appendWatermark(canvas.toDataURL(), markWidth)
        }
        img.crossOrigin = 'anonymous'
        img.referrerPolicy = 'no-referrer'
        img.src = image
      } else {
        this.fillTexts(ctx, drawX, drawY, drawWidth, drawHeight)
        /** Fill the interleaved text after rotation */
        ctx.restore()
        this.rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
        this.fillTexts(ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
        this.appendWatermark(canvas.toDataURL(), markWidth)
      }
    },
    destroyWatermark() {
      if (!watermarkRef) return
      watermarkRef.remove()
      watermarkRef = undefined
    },
  },

  computed: {
    markStyle() {
      const props = this.$props
      const [gapX, gapY] = props.gap
      const [offsetX, offsetY] = props.offset

      const gapXCenter = gapX / 2
      const gapYCenter = gapY / 2
      const offsetTop = offsetY || gapYCenter
      const offsetLeft = offsetX || gapXCenter

      const markStyle = {
        zIndex: this.zIndex,
        position: 'absolute',
        left: 0,
        top: 0,
        width: '100%',
        height: '100%',
        pointerEvents: 'none',
        backgroundRepeat: 'repeat',
      }

      let positionLeft = offsetLeft - gapXCenter
      let positionTop = offsetTop - gapYCenter
      if (positionLeft > 0) {
        markStyle.left = `${positionLeft}px`
        markStyle.width = `calc(100% - ${positionLeft}px)`
        positionLeft = 0
      }
      if (positionTop > 0) {
        markStyle.top = `${positionTop}px`
        markStyle.height = `calc(100% - ${positionTop}px)`
        positionTop = 0
      }
      markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`

      return markStyle
    },
  },

  beforeDestroy() {
    this.destroyWatermark()
    this.observe = null
  },
  //  End
}
</script>

<style lang="scss" scoped>
.watermark-container {
  position: relative;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/509735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24.eslint

eslint是约束代码写法的插件&#xff0c;比如组件的命名必须要用驼峰命名这种 eslint官网 检测并修复 JavaScript 代码中的问题。 - ESLint - 插件化的 JavaScript 代码检查工具 目录 1 vue-cli的eslint 2 标准规则 2.1 不能连续出现两个空行 2.2 结尾必须有空行 2.3…

深入了解Dubbo SPI 工作机制——@Activate (5)

在上一篇Dubbo 基于xml文件分析主流程源码 &#xff08;4&#xff09;_chen_yao_kerr的博客-CSDN博客中, 我们已经初步了解了Dubbo SPI的 key - value 结构。接下来将会继续分享Dubbo SPI其他功能的使用方式&#xff0c;并且从源码的角度去一谈究竟。 Activate注解 参数名 …

【数据结构】链表OJ:力扣141.环形链表、142.环形链表II

今天要分享的关于链表的题目是环形链表 目录 题目141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 题解 关于快慢指针的深入研究 题目2&#xff1a;142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题解 以下是题目链接 141. 环形链表 - 力扣&#xff…

塑料回收---未来化工行业的新兴增长领域

大量的旧塑料被浪费 从南极洲到北极&#xff0c;在原始海岸线上冲刷的塑料废物&#xff0c;以及太平洋上巨大的塑料废物浮岛&#xff0c;得到了媒体的广泛报道&#xff0c;并促成了消费者消费意识发生转变。 研究表明&#xff0c;大多数废旧塑料被送往垃圾填埋场和焚烧&#…

Go语言设计模式之责任链模式

其实很多人不知道,责任链模式是我们工作中经常遇到的模式,特别是web后端工程师,我们工作中每时每刻都在用:因为市面上大部分的web框架的过滤器基本都是基于这个设计模式为基本模式搭建的。 1.模式介绍 我们先来看一下责任链模式(Chain Of Responsibility Design Pattern…

react实现点击获取json对象的jsonPath

准备 安装 react-json-view&#xff1a;npm install --save react-json-view 可参考的一些开源库&#xff1a;react-json-path-picker&#xff0c;json-path-picker 线上工具&#xff1a;jsonpath tool JsonPath JsonPath官方文档 用来解析多层嵌套的json数据。JsonPath 是一…

8分钟的面试,我直呼太变态了......

干了两年外包&#xff0c;本来想出来正儿八经找个互联网公司上班&#xff0c;没想到算法死在另一家厂子。 自从加入这家外包公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到11月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资…

08 FPGA—计数器与分频器的应用

1. 理论 时序逻辑电路中最基本的单元—寄存器&#xff0c;我们可以使用寄存器来做计数器。基本上关于时间的设计都离不开计数器。 计数器在数字系统中主要是对脉冲的个数进行计数&#xff0c;以实现测量、计数和控制的功能&#xff0c;同时兼有分频功能。计数器一般都是从 0 开…

JSON-框架的具体使用

JSON-框架的具体使用 非 SpringBoot 项目 Jackson Jackson 是另一个流行的JSON序列化和反序列化库&#xff0c;具有以下特点 速度快&#xff1a;Jackson 采用了高效的JSON解析算法和字节码生成技术&#xff0c;使得其序列化和反序列化速度非常快。支持全类型序列化&#xff1…

V8 过去版本的性能提升汇总

&#xff08;预测未来最好的方法就是把它创造出来——尼葛洛庞帝&#xff09; V8 官方链接 NodeJs8.3之前的代码优化建议 NodeJs8.3版本之后的turbofan虚拟机引擎 编写性能更高的JavaScript代码 chromium 优化博客 chrom v8版本发布路线图 V8 是 Google 的开源高性能 JavaScri…

unity 实现水的波纹效果

之前的实现过这个效果&#xff0c;可惜没有记笔记&#xff0c;所以现在有点遗忘&#xff0c;连多个波纹一起在水面上实现的效果都忘记了&#xff0c;所以&#xff0c;查看了下之前实现的代码&#xff0c;现在再记一下笔记。 基础的波纹效果 要实现波纹&#xff0c;首先要知道…

技术转管理,先来试试管理好项目

今天分享的主题是&#xff1a;如果你想技术转管理&#xff0c;先来试试管好一个项目 技术转管理&#xff0c;是很多技术人员的梦想&#xff0c;这也是30多岁之前还在做技术的人&#xff0c;也会对自己常常发出居安思危的意识表现&#xff0c;所以经常有人问我&#xff0c;怎么样…

chatGPT润色中英论文软件-文章修改润色器

chatGPT可以润色英文论文吗&#xff1f; ChatGPT可以润色英文论文&#xff0c;它具备自动纠错、自动完善语法和严格全面的语法、句法和内容结构检查等功能&#xff0c;可以对英文论文进行高质量的润色和优化。此外&#xff0c;ChatGPT还支持学术翻译润色、查重及语言改写等服务…

Java每日一练(20230510) 生成器类、螺旋矩阵II、删除链表的重复元素II

目录 1. 定义一个类Generator &#x1f31f;&#x1f31f; 2. 螺旋矩阵 II &#x1f31f;&#x1f31f; 3. 删除排序链表中的重复元素 II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日…

Linux 设备树

1 什么是设备树&#xff1f; 设备树(Device Tree)&#xff0c;将这个词分开就是“设备”和“树”&#xff0c;描述设备树的文件叫做 DTS(Device Tree Source)&#xff0c;这个 DTS 文件采用树形结构描述板级设备&#xff0c;也就是开发板上的设备信息&#xff0c;比如 CPU 数量…

【Proteus仿真】| 05——问题记录

系列文章目录 【Proteus仿真】| 01——软件安装 【Proteus仿真】| 02——基础使用 【Proteus仿真】| 03——超详细使用教程 【Proteus仿真】| 04——绘制原理图模板 【Proteus仿真】| 05——问题记录 文章目录 前言1、51单片机仿真2、stm32仿真1. stm32 adc 采集电压一直为0 3、…

显卡3080设备CentOS 7.9 环境安装最新anconda、tensorflow-gpu 、cudatoolkit、cudnn、 python

目标&#xff1a;使用3080显卡搭建环境 系统安装 显卡驱动安装&#xff1a; 安装anconda 安装 python 安装 :cuda 安装&#xff1a;cudnn 安装 :tensorflow 一&#xff1a;系统安装&#xff1a;详见历史文档 二&#xff1a;显卡驱动安装&#xff1a;详见历史 三&#xff1a;整…

安装2023最新版_华为欧拉操作系统_OpenEuler操作系统_并配置IP地址_联网---linux工作笔记055

强调,一定要记得,硬盘多给点,50G根本不够用,搭建集群的话,自己测试都要100G才行哈.. 要不然麻烦,因为别的可以动态修改,但是硬盘大小修改了,不起作用,需要在 linux中再设置分区很麻烦 https://www.openeuler.org/zh/download/ 首先去下载安装包 然后找到这个安装包下载 然…

虚拟机中linux操作系统如何连网

文章目录 方法镜像来源本文前提创建centos7虚拟机1. 创建新的虚拟机&#xff0c;选择典型配置2. 安装来源选择上述下载的centos3. 命名虚拟机时注意事项如下图所示4. 后面配置硬盘大小默认20GB足以&#xff0c;然后调整虚拟机设置&#xff0c;可参考下图5.运行虚拟机 实操建议 …

混频器IP3的测量以及测试误差的来源分析

混频器线性度一直是射频系统设计面临的一个关键问题。混频器的非线性会产生不需要的、不可滤的杂散、互调和非线性失真。例如&#xff0c;非线性混频可能导致不希望的杂散&#xff0c;例如2fRF✕2fLO 或2fRF✕fLO 频率分量&#xff0c;加剧射频系统频谱再生问题。 1、IP3和IMD…