手写添加水印代码

60 阅读1分钟

需求:写一个水印组件 父组件传递一个watermark的对象数据,子组件接受数据,并显示。 子组件代码如下:

<template>
  <div
    class="watermark-container"
    :style="{
      position: 'relative',
      // zIndex: 9999,
    }"
  >
    <slot></slot>
    <div
      class="watermark"
      :style="{
        position: 'absolute',
        top: watermark.top + 'px',
        width: `${width}px`,
        height: `${height}px`,
        pointerEvents: 'none',
        zIndex: 9999,
      }"
    >
      <canvas
        ref="canvasRef"
        :style="{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          transform: `rotate(${watermark.rotate}deg)`,
        }"
      ></canvas>
    </div>
  </div>
</template>

<script setup>
import { watch } from 'vue';
import { ref, onMounted, computed, onUnmounted } from 'vue';

const props = defineProps({
  watermark: {
    type: Object,
    default: true,
  },
});

const canvasRef = ref(null);
watch(
  () => props.watermark,
  () => {
    const elImage = document.querySelector('.watermark-container .el-image');
    width.value = elImage.offsetWidth;
    // 获取元素的高度
    height.value = elImage.offsetHeight;
    drawWatermark();
  },
  { deep: true }
);
const width = ref('');
const height = ref('');
onMounted(() => {
  // /
  // 获取页面上元素el-image的宽高
  const elImage = document.querySelector('.watermark-container .el-image');
  // 获取元素的宽度
  width.value = elImage.offsetWidth;
  // 获取元素的高度
  height.value = elImage.offsetHeight;
});

const canvasWidth = computed(() => {
  return window.innerWidth - props.leftOffset - props.rightOffset;
});

const canvasHeight = computed(() => {
  return window.innerHeight - props.topOffset - props.bottomOffset;
});

const drawWatermark = async () => {
  const canvas = canvasRef.value;
  const ctx = canvas.getContext('2d', { willReadFrequently: true });
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  if (props.watermark.text) {
    ctx.font = `${props.watermark.fontSize}px Arial`;
    ctx.fillStyle = props.watermark.color;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    for (let x = 0; x < canvas.width; x += props.watermark.xSpace) {
      for (let y = 0; y < canvas.height; y += props.watermark.ySpace) {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate((props.watermark.rotate * Math.PI) / 180);
        ctx.fillText(props.watermark.text, 0, 0);
        ctx.restore();
      }
    }
  } else if (props.watermark.imgUrl) {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.src = props.watermark.imgUrl;

    await new Promise((resolve, reject) => {
      img.onload = resolve;
      img.onerror = reject;
    });

    ctx.globalAlpha = props.watermark.alpha;

    for (let x = 0; x < canvas.width; x += props.watermark.xSpace) {
      for (let y = 0; y < canvas.height; y += props.watermark.ySpace) {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate((props.watermark.rotate * Math.PI) / 180);
        ctx.drawImage(img, 0, 0, props.watermark.fontSize, 10);
        ctx.restore();
      }
    }
  }
};

onMounted(() => {
  drawWatermark();
  window.addEventListener('resize', drawWatermark);
});

onUnmounted(() => {
  window.removeEventListener('resize', drawWatermark);
});
</script>

<style scoped lang="scss">
.watermark-container {
  width: 100%;
  height: 100%;
  .el-image {
    width: 100%;
  }
}

.watermark {
  overflow: hidden;
}
</style>

根据父组件中传递过来的时text还是img,循环添加水印,根据父组件中的xSpace和ySpacs分别代表水平间距和垂直间距画图像。 以下是父组件使用水印组件的方法,将img标签插入子组件的slot插槽中进行使用

<Watermark :watermark="{ text: '水印文本', fontSize: 20, color: 'gray', rotate: -30, xSpace: 100, ySpace: 150 }">
    <!-- 这里的内容会被插入到子组件的 <slot> 位置 -->
    <img src="example.jpg" alt="示例图片" class="el-image" />
  </Watermark>