vue 水印组件

效果图展示![在这里插入图片描述](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.kler.cn/a/18513.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机图形学基础教程】MFC基本绘图函数2

MFC基本绘图函数 绘图工具类 CGdiObject类&#xff1a;GDI绘图工具的基类CBitmap类&#xff1a;封装了GDI画刷&#xff0c;可以选作设备上下文的当前画刷&#xff0c;用于填充图形的内部CFont类&#xff1a;封装了GDI字体&#xff0c;可以选作设备上下文的当前字体CPalette类…

selenium——unittest框架

目录 一、unittest框架基本介绍二、unittest框架解析三、unittest框架使用方法1.测试固件2.测试套件3.用例的执行顺序4.忽略测试用例中的方法5.unittest断言6.HTML报告生成 一、unittest框架基本介绍 在进行selenium IDE脚本录制导出的脚本中&#xff0c;我们发现其中多了很多…

基于 EKS Fargate 搭建微服务性能分析系统

背景 近期 Amazon Fargate 在中国区正式落地&#xff0c;因 Fargate 使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&…

mysql数据迁移与同步常用解决方案总结

目录 一、前言 二、数据迁移场景 2.1 整库迁移 2.2 表数据迁移 2.3 mysql版本变更 2.4 mysql数据迁移至其他存储介质 2.5 自建数据到上云环境 2.6 mysql数据到其他国产数据库 三、数据库物理迁移实施方案 3.1 数据库物理迁移概述 3.1.1 物理迁移适用场景 3.1.2 物理…

TCP三次握手

TCP三次握手 文章目录 TCP三次握手1. TCP三次握手过程和状态变迁1. 准备工作2. 进行连接 2. 能把三次握手改为两次握手吗&#xff1f;3. 改为两次握手会出现什么后果&#xff1f;4. 改为四次握手行不行&#xff1f;5. TCP第三次握手失败了怎么办&#xff1f;6. 三次握手是否可以…

TCP四次挥手

TCP四次挥手详解 文章目录 TCP四次挥手详解1. TCP四次挥手过程和状态变迁2. 为什么挥手需要四次&#xff1f;3. 为什么中间的ACK和FIN不可以像三次握手那样合为一个报文段呢&#xff1f;4. 为什么TIME_WAIT等待的时间是2MSL&#xff1f;5. 等待2MSL的意义5.1 保证客户端最后发送…

Linux网络——Shell编程之数组

Linux网络——Shell编程之数组 一、概念二、数组的定义三、Shell数组操作1. 获取数组的所有元素的列表2. 获取数组的所有元素下标3.取数组的元素个数4. 获取数组的某个元素的值5.删除数组某个元素6.删除数组7.数组切片8.数组字符替换9.数组追加元素 四、数组在函数的传参 一、概…

天猫数据分析:2023年Q1天猫净水器品牌销售TOP10排行榜

水质的好坏更是与人们的身体健康密切相关。随着社会经济的发展&#xff0c;居民生活水平提升&#xff0c;人们对饮用水质量、安全性的要求也不断提高&#xff0c;净水器也因此逐渐成为现代生活的必需品。 根据鲸参谋电商数据显示&#xff0c;2023年Q1在天猫平台上&#xff0c;净…

3. SQL底层执行原理详解

一条SQL在MySQL中是如何执行的 1. MySQL的内部组件结构1.1 Server层1.2 Store层 2. 连接器3. 分析器4. 优化器5. 执行器6. bin-log归档 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 1. MySQL的内部组件结…

MVC分部视图的使用:Html.Partial/RenderPartial,Html.Action/RenderAction,RenderPage

ASP.NET MVC 里的部分视图&#xff0c;相当于 Web Form 里的 User Control。我们的页面往往会有许多重用的地方&#xff0c;可以进行封装重用。 使用部分视图有以下优点&#xff1a; 1. 可以简写代码。 2. 页面代码更加清晰、更好维护。 在视图里有多种方法可以 加载部分视图&a…

硬盘数据突然消失怎么回事?硬盘数据突然消失怎么找回

硬盘上的数据对每个人都至关重要&#xff0c;它可能是我们的珍贵回忆&#xff0c;多年学习的总结&#xff0c;或者一些不可告人的秘密。而硬盘中的数据可能会在不知情的情况下消失或被删除&#xff0c;这种情况对我们来说十分痛苦和困扰。然而&#xff0c;我们不必担心&#xf…

如何用100天彻底学会Python?

Python 是一门功能强大、易于学习且历史悠久的编程语言。如果你希望在短时间内彻底学会 Python&#xff0c;需要制定一个全面的学习计划&#xff0c;并进行刻意的练习和实践。 以下是一份建议的学习计划&#xff0c;帮助你在 100 天内掌握 Python 技能。 第 1-10 天&#xff…

JavaScript class和继承的原理

&#xff08;对于不屈不挠的人来说&#xff0c;没有失败这回事。——俾斯麦&#xff09; class 相关链接 MDN链接 有关类的详细描述 关于构造函数&#xff0c;原型和原型链的说明 类的概述 类是用于创建对象的模板。他们用代码封装数据以处理该数据。JS 中的类建立在原型上…

【Queue新技法】用双数组实现一个队列 C++

目录 1 常规的队列构建2 加入一些限制2-1形式化说明2-2 优化&#xff1a;平衡队列 附录0 双数组或双链表实现队列1 单链表与循环缓冲区实现队列3 参考资料 1 常规的队列构建 到火车站办理退票&#xff0c;排队的人构成队列。注意到有两个关键动作&#xff1a; 入队&#xff0c…

C++类和对象(上)

目录 一、类 1.1 类的引入 1.2 类的定义 1.3 类的访问限定符及封装 1.3.1 类的访问限定符 1.3.2 封装 1.4 类定义方式 1.4.1 声明和定义都放在类体里 1.4.2 类可以声明和定义分离 1.5 类的作用域 1.6 类的实例化 1.6.1 定义 1.6.2 计算类对象的大小 二、this指针…

华为OD机试真题 Java 实现【猜字谜】【2023Q2】

一、题目描述 小王设计了一人简单的清字谈游戏&#xff0c;游戏的迷面是一人错误的单词&#xff0c;比如nesw&#xff0c;玩家需要猜出谈底库中正确的单词。猜中的要求如 对于某个谜面和谜底单词&#xff0c;满足下面任一条件都表示猜中&#xff1a; 变换顺序以后一样的&…

【MySQL】索引

之前的select所采用的方式是遍历数据表进行查找,这种方式效率是比较低的,尤其是数据表汇总数据量比较大的时候,于是便有了索引,MySQL中的索引的作用就是为了快速获取数据 文章目录 1.索引简介2.索引的数据结构2.1 B-Tree2.2 Hash索引 3.索引的分类4.索引的语法5.SQL性能分析6.…

字节跳动发放年终奖,远超预期~

最近一段时间&#xff0c;国内互联网大厂接连公布年终奖情况&#xff0c;整个后厂村都洋溢在春节般的喜庆气氛里。 虽然由于各种各样的顾虑&#xff08;主要是人员流失问题&#xff09;&#xff0c;大部分公司都将年终奖发放时间调整到了年中&#xff0c;但好饭不怕晚&#xf…

Java笔记_17(异常、File)

Java笔记_17 一、异常1.1、异常体系介绍1.2、编译时异常和运行时异常1.3、异常的作用1.4、异常的处理方式1.5、捕获异常的灵魂四问1.6、异常中的常见方法1.7、抛出处理1.8、异常-练习&#xff08;键盘录入数据&#xff09;1.9、自定义异常 二、File2.1、File的概述和构造2.2、F…

uboot 启动内核代码分析

0、uboot和内核区别 uboot的本质就是一个复杂点的裸机程序。内核本身也是一个"裸机程序“&#xff0c;和uboot、和其他裸机程序并没有本质区别。 区别就是操作系统运行起来后在软件上分为内核层和应用层&#xff0c;分层后两层的权限不同&#xff0c;在内存访问和设备操作…
最新文章