实战:前端项目中Watermark水印组件的实现过程

2,728 阅读4分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

前言

2022首次更文挑战已经断更三天了,有存货但是没有发,毕竟想搏一搏春节幸运奖,梦想还是要有的,万一实现了呢?对于此次更文我本身没有打算全勤40天的计划,反正能更多少天算多少天,主要还是为了给自己留下记录,顺便把自己遇到的问题分享出来,避免大家再去踩坑。

这次主要来聊聊如何封装一个Vue的水印组件?这次不是React了哦,毕竟React相搭配的Antd pro提供的水印组件还是很优秀的,Vue项目用别人的又怕删库跑路倒是GG了,所以还是自己动手,丰衣足食吧。

需求背景

好了,来水印组件的需求没有多复杂,就是一句话:所有页面给加上水印,水印文字为用户姓名和手机号码。其实这里为什么要封装Vue的水印组件,主要是因为我们的系统是一个融合平台,主平台的开发技术栈是Vue,而嵌入的页面是React,而嵌入的页面我们使用了Antd pro的水印组件,所以产品认为拿过来使用就行了,听到我就想笑,不懂技术的产品还真是可爱呀^_^。没办法,既然没有那我们就需要造轮子,以和为贵。

开发

既然说到了ReactVue中都使用了水印组件,那我们就在开发里面都说说两者是如何使用的,先来说说Vue项目中是如何封装的,毕竟别人的是现成的拿过来直接使用就行,讲讲用法就好。

Vue项目中的水印组件

其实封装组件不知道如何下手时,先去看看别人现有的组件是怎么实现的,参考一下实现思路和样式布局这些,这样就会灵感大开,自然就会手到擒来了。我也是参考了Antd pro的水印组件的实现,只不过技术变成了Vue而已。好了,纸上得来终觉浅,开撸。

创建Watermark.vue文件:

<template>
  <div :style="overlapStyle">
    <slot></slot>
    <div class="bit-mark-wrap" :style="{'background-image': `url(${base64URL})`}"></div>
    <canvas class="bit-watermark-canvas" ref="canvas" width="450" height="320" />
  </div>
</template>

<script>
  /**
   * 背景水印组件
   */
  export default {
    name: 'Watermark',
    props: {
      text: String,
      overlapStyle: {
        type: String,
        default () {
          return ''
        }
      }
    },
    data () {
      return {
        base64URL: ''
      }
    },
    mounted () {
      this.drawInit()
    },
    methods: {
      drawInit: function () {
        const { text } = this
        let ctx = this.$refs.canvas.getContext('2d')
        ctx.width = 120
        ctx.height = 160
        ctx.font = '22px normal'
        ctx.fillStyle = 'rgba(0, 0, 0, .15)'
        ctx.rotate(-22 * Math.PI / 180)
        ctx.fillText(text, 0, 120)
        // 转base64图片
        this.base64URL = this.$refs.canvas.toDataURL('image/png')
      }
    }
  }
</script>

<style type="text/css">
  .bit-watermark-canvas {
    position: fixed;
    bottom: -1000px;
    right: -1000px;
  }
  
  .bit-mark-wrap {
    z-index: 9;
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 100%;
    background-size: 460px;
    pointer-events: none;
    background-repeat: repeat;
  }
</style>

从上面代码可以看到,水印的核心还是通过canvas绘制,然后将绘制的内容转成base64格式图片,通过样式控制其是否平铺重复展示。但是可以看到在封装这个组件很多属性的值都是写死的,对于变化的需求来说达不到很好的复用性,所以需要优化代码,使组件的复用性更强。

优化后:

<template>
  <div :style="overlapStyle">
    <slot></slot>
    <div 
        class="bit-mark-wrap" 
        :style="{'background-image': `url(${base64URL})`, 'background-size': `${gapX}px ${gapY}px`}"
    ></div>
    <canvas class="bit-watermark-canvas" ref="canvas" width="450" height="320" />
  </div>
</template>

<script>
  /**
   * 背景水印组件
   */
  export default {
    name: 'Watermark',
    props: {
      text: String,
      overlapStyle: {
        type: String,
        default () {
          return ''
        }
      },
      width: {
        type: Number,
        default () {
          return 120
        }
      },
      height: {
        type: Number,
        default () {
          return 160
        }
      },
      fontSize: {
        type: Number,
        default () {
          return 16
        }
      },
      fontColor: {
        type: String,
        default () {
          return '#ccc'
        }
      },
      rotate: {
        type: Number,
        default () {
          return -22
        }
      },
      gapX: {
        type: Number,
        default () {
          return 320
        }
      },
      gapY: {
        type: Number,
        default () {
          return 222
        }
      }
    },
    data () {
      return {
        base64URL: ''
      }
    },
    mounted () {
      this.drawInit()
    },
    methods: {
      drawInit: function () {
        const { text, width, height, fontSize, fontColor, rotate } = this
        let ctx = this.$refs.canvas.getContext('2d')
        ctx.width = width
        ctx.height = height
        ctx.font = `${fontSize}px normal`
        ctx.fillStyle = fontColor
        ctx.rotate(rotate * Math.PI / 180)
        ctx.fillText(text, 0, height)
        // 转base64图片
        this.base64URL = this.$refs.canvas.toDataURL('image/png')
      }
    }
  }
</script>

<style lang="scss" type="text/css">
  .bit-watermark-canvas {
    position: fixed;
    bottom: -1000px;
    right: -1000px;
  }
  
  .bit-mark-wrap {
    z-index: 9;
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 100%;
    background-size: 320px;
    pointer-events: none;
    background-repeat: repeat;
  }
</style>

可以看到我把之前代码写死的属性都搞成了参数,这样可以动态传入,方便各个页面复用时有不同的需求样式。具体的参数参考以下表格:

API:

参数说明类型默认值
width水印的宽度number120
height水印的高度number160
rotate水印绘制时,旋转的角度,单位°number-22
fontColor水印文字颜色string#ccc
fontSize文字大小string  number16
overlapStyle水印层的样式React.CSSProperties-
text水印文字内容string-
gapX水印之间的水平间距number320
gapY水印之间的垂直间距number222

这些参数中重点说一下rotategapXgapY三个参数:rotate控制水印的旋转角度,默认为-22度;gapX是控制水印在页面中水平间距,即每一行显示多少个水印;gapY则是控制水印在页面中垂直间距,即每一列显示多少个水印。

在上面代码中可以看到代码格式没有分号;,这主要是由于我们的Vue项目使用了Eslint规则,配置了不使用分号,但是在我们其他项目并没有采用Eslint,小伙伴们看文章时不要认为我的代码习惯不好,其实我是有代码洁癖的^_^。

使用方法:

1)导入组件
import Watermark from '../components/Watermark'

components: {
  Watermark
}

2)使用组件:
// 效果见图1
<watermark overlapStyle="height: 100%;" :text="'张三130xxxx0000'">
    <router-view></router-view>
</watermark>

// 效果见图2
<watermark
  overlapStyle="height: 100%;"
  :text="'张三130xxxx0000'"
  :rotate="-30"
  :height="240"
  :gap-x="320"
  :gap-y="320"
  :font-size="18"
  :font-color="'red'"

>
  <router-view></router-view>
</watermark>

效果:

微信截图_20220126152421.png

微信截图_20220126152516.png

为了展示效果,我将水印组件套在了路由router-view的外面,并不代表组件只能这样引用,所以小伙伴们不要误解。水印组件在项目需要的页面都可以引用使用,我只是方便展示效果省事就这样使用,这样使用系统所有的页面都会加上水印,具体需求按照各自真实需求自行引用即可。

React项目中的水印组件

React项目中使用水印组件,我们就没有必要自己写一个了,Antd pro提供的水印组件足以满足项目需求,而且可能还比我们自己写得好用,我之所以封装Vue的是因为没有现成的,只有自己来,所以React项目中的使用水印组件我只说用法就好,望周知。

安装插件:

我们按需安装pro-layout就好,没有必要安装所有Antd pro所有插件。

npm i @ant-design/pro-layout 或 yarn i @ant-design/pro-layout

使用:

1)导入组件:
import {WaterMark} from "@ant-design/pro-layout";

2)使用组件:
<WaterMark {
    ...{...getWaterMarkProps(),
        gapX: 120,
        gapY: 140,
    }
}>
    <div className="bit-chart-box">
        <div>...</div>
    </div>
        
</WaterMark>

可以看到先定义了一个getWaterMarkProps()获取水印组件属性的方法,在里面保证全部页面都有一个统一的样式,而至于需要特性化的页面通过覆盖属性值去做修改并达到不同页面不同效果的目的。

getWaterMarkProps()方法如下:

/**
 * 获取水印属性
 */
export const getWaterMarkProps = (): any => {
    const {employee, mobile} = getUserInfo();
    let props = {
        content: `${employee ? employee : ''} ${mobile ? mobile : ''}`,
        gapX: 220,
        gapY: 140,
        offsetTop: 100,
        fontColor: 'rgba(255, 255, 255, 0.1)'
    }
    return props;
}

API:

具体API可以通过API文档链接进行查看使用。

微信截图_20220126162327.png

微信截图_20220126162348.png

效果:

微信截图_20220126155215.png

至此,关于在项目中如何封装水印组件,使用水印组件的方法就差不多了,解决问题的方法很多,我只是提供了一种解决办法而已,并且我相信小伙伴们还有更好的解决办法,我只是做一个抛砖引玉之人,更多地方法需要大家一起去优化提升。

纸上得来终觉浅,绝知此事要躬行——陆游

总之,我始终认为技术这条路要想走远并不是看看就能学会,当然不排除天才,所以更多地还是得自己实践实践,上手撸一撸代码方能牢牢掌握。临近过年,这是年前最后一次更文了,新年也会更文,到时的更文就是博运气薅羊毛了,但是我能保证的是绝不是水文,毕竟我对水文是厌恶至极,也不会写水文。好了,最后祝大家身体健康,万事如意,虎年大吉💖。

往期精彩文章

后语

伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注在走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。