my-watermark/src/Watermark.vue

<template>
  <div :class="classes" :style="styles">
    <slot></slot>
    <canvas ref="canvas"
            :width="width"
            :height="height"
            style="display:none"></canvas>
    <div v-if="mask" class="my-watermark__mask" :style="maskStyle"></div>
  </div>
</template>


<script>
  /**
   * 水印组件
   * @module $ui/components/my-watermark
   */
  export default {
    name: 'MyWatermark',
    /**
     * 属性参数
     * @member props
     * @property {String|Array} text 水印文字, 多行文本可以传数组
     * @property {Number} [lineHeight=10] 行距,多行文本有效
     * @property {Number} [width=200] 画布的宽度
     * @property {Number} [height=200] 画布的高度
     * @property {String} [font] 水印文字的字体
     * @property {Number} [rotate] 文字旋转角度,默认:-20
     * @property {String} [color] 水印文字字体颜色 默认:rgba(100, 100, 100, 0.1)
     * @property {Number} [x] 文字在画布的坐标x
     * @property {Number} [y] 文字在画布的坐标y
     * @property {Boolean} [mask] mask 遮罩模式
     */
    props: {
      disabled: Boolean,
      text: {
        type: [String, Array],
        default: ''
      },
      lineHeight: {
        type: Number,
        default: 10
      },
      width: {
        type: Number,
        default: 300
      },
      height: {
        type: Number,
        default: 300
      },
      font: {
        type: String,
        default: '20px 黑体'
      },
      rotate: {
        type: Number,
        default: -20
      },
      color: {
        type: String,
        default: 'rgba(100, 100, 100, 0.1)'
      },
      x: {
        type: Number,
        default: 0
      },
      y: {
        type: Number,
        default: 100
      },
      mask: {
        type: Boolean,
        default: true
      }
    },
    data() {
      return {
        dataUrl: null
      }
    },
    computed: {
      classes() {
        return {
          'my-watermark': true,
          'is-mask': this.mask,
          'is-disabled': this.disabled
        }
      },
      styles() {
        if (!this.dataUrl || this.mask || this.disabled) return null

        return {
          backgroundImage: `url(${this.dataUrl})`
        }
      },
      maskStyle() {
        if (!this.dataUrl || !this.mask || this.disabled) return null

        return {
          backgroundImage: `url(${this.dataUrl})`
        }
      }
    },
    methods: {
      draw() {
        if (!this.text) return
        const textArray = [].concat(this.text)
        const canvas = this.$refs.canvas
        const ctx = canvas.getContext('2d')
        const rotate = this.rotate * Math.PI / 180
        ctx.clearRect(0, 0, this.width, this.height)
        ctx.rotate(rotate)
        ctx.font = this.font
        ctx.fillStyle = this.color
        const fontSize = parseInt(ctx.font) || 20
        textArray.forEach((text, index) => {
          const y = this.y + (fontSize + this.lineHeight) * index
          ctx.fillText(text, this.x, y)
        })
        this.dataUrl = ctx.canvas.toDataURL()
      },
      reset() {
        const canvas = this.$refs.canvas
        const ctx = canvas.getContext('2d')
        ctx.clearRect(0, 0, this.width, this.height)
        const rotate = this.rotate * Math.PI / 180
        ctx.rotate(-rotate)
      }
    },
    watch: {
      text() {
        this.reset()
        this.draw()
      }
    },
    mounted() {
      !this.disabled && this.draw()
    }
  }
</script>