基于canvas实现的签名组件

基于canvas实现的签名组件

<script setup lang="ts">
// import { uploadFileApi } from '@/api/file';
import { ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';

const imgUrl = ref('');
const canvas = ref();
let ctx: CanvasRenderingContext2D;

// 正在绘制中,用来控制 move 和 end 事件
let painting = false;

// 获取触发点相对被触发dom的左、上角距离
const getOffset = (event: MouseEvent | TouchEvent) => {
  let offset: [number, number];
  if ((event as MouseEvent).offsetX) {
    // pc端
    const { offsetX, offsetY } = event as MouseEvent;
    offset = [offsetX, offsetY];
  } else {
    // 移动端
    const { top, left } = canvas.value.getBoundingClientRect();
    const offsetX = (event as TouchEvent).touches[0].clientX - left;
    const offsetY = (event as TouchEvent).touches[0].clientY - top;
    offset = [offsetX, offsetY];
  }

  return offset;
};

// 绘制起点
let startX = 0,
  startY = 0;

// 鼠标/触摸 按下时,保存 触发点相对被触发dom的左、上 距离
const onEventStart = (event: MouseEvent | TouchEvent) => {
  [startX, startY] = getOffset(event);
  painting = true;
};

const onEventMove = (event: MouseEvent | TouchEvent) => {
  if (painting) {
    // 鼠标/触摸 移动时,保存 移动点相对 被触发dom的左、上 距离
    const [endX, endY] = getOffset(event);
    paint(startX, startY, endX, endY, ctx);

    // 每次绘制 或 清除结束后,起点要重置为上次的终点
    startX = endX;
    startY = endY;
  }
};

const onEventEnd = () => {
  if (painting) {
    painting = false; // 停止绘制
  }
};

onMounted(() => {
  ctx = canvas.value.getContext('2d') as CanvasRenderingContext2D;
});
const handleToFile = async () => {
  if (isCanvasBlank(canvas.value)) {
    message.warning('当前签名文件为空');

    return;
  }
  //将图片转为文件调接口传给后端
  const file = dataURLtoFile(canvas.value.toDataURL(), '签名.png');

  if (!file) return;
  // const { data } = await uploadFileApi(file);
  handleClearSign();
  // imgUrl.value = data.url;
};
const handleClearSign = () => {
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
};
const isCanvasBlank = (canvas: HTMLCanvasElement) => {
  const blank = document.createElement('canvas'); //系统获取一个空canvas对象
  blank.width = canvas.width;
  blank.height = canvas.height;
  return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
};

// 保存为图片
const handleSaveImg = () => {
  if (isCanvasBlank(canvas.value)) {
    message.warning('当前签名文件为空');

    return;
  }
  const el = document.createElement('a');
  // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式
  el.href = canvas.value.toDataURL();
  el.download = '签名';
  // 创建一个点击事件并对 a 标签进行触发
  const event = new MouseEvent('click');
  el.dispatchEvent(event);
};
// 转为file格式,可传递给后端
const dataURLtoFile = (dataurl: string, filename: string) => {
  const arr: string[] = dataurl.split(',');
  if (!arr.length) return;

  const mime = arr[0].match(/:(.*?);/);
  if (mime) {
    const bstr = atob(arr[1]);
    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, { type: mime[1] });
  }
};
// canvas 画图
function paint(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D,
) {
  ctx.beginPath();
  ctx.globalAlpha = 1;
  ctx.lineWidth = 2;
  ctx.strokeStyle = '#000';
  ctx.moveTo(startX, startY);
  ctx.lineTo(endX, endY);
  ctx.closePath();
  ctx.stroke();
}

// 橡皮
function eraser(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D,
  size: number,
  shape: 'rect' | 'circle',
) {
  ctx.beginPath();
  ctx.globalAlpha = 1;
  switch (shape) {
    case 'rect':
      ctx.lineWidth = size;
      ctx.strokeStyle = '#fff';
      ctx.moveTo(startX, startY);
      ctx.lineTo(endX, endY);
      ctx.closePath();
      ctx.stroke();
      break;
    case 'circle':
      ctx.fillStyle = '#fff';
      ctx.arc(startX, startY, size, 0, 2 * Math.PI);
      ctx.fill();
      break;
  }
}
</script>
<template>
  <div class="canvas-dom">
    <h3>基于canvas实现的签名组件</h3>
    <header>
      <a-button type="primary" @click="handleSaveImg">保存为图片</a-button>
      <a-button @click="handleToFile"> 保存到后端 </a-button>
      <a-button @click="handleClearSign"> 清空签名 </a-button>
    </header>
    <canvas
      ref="canvas"
      height="200"
      width="500"
      @mousedown="onEventStart"
      @mousemove.stop.prevent="onEventMove"
      @mouseup="onEventEnd"
      @touchstart="onEventStart"
      @touchmove.stop.prevent="onEventMove"
      @touchend="onEventEnd"
    >
    </canvas>
    <!-- <img v-if="imgUrl" :src="imgUrl" alt="签名" /> -->
  </div>
</template>
<style scoped lang="scss">
.canvas-dom {
  width: 100%;
  height: 100%;
  padding: 0 20px;
  background-color: #fff;

  canvas {
    border: 1px solid #e6e6e6;
  }

  header {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    width: 100%;
    margin: 8px;

    .eraser-option {
      display: flex;

      label {
        white-space: nowrap;
      }
    }
  }
}
</style>


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜空孤狼啸

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值