开源分享: 基于vue3的电子签名组件

b43d3dde83a60ba93fd622fc6081bebb.png

github地址: https://github.com/open-vue3/vue3-sign

hello, 大家好, 我是徐小夕, 今天又到了分享时间. 之前和大家分享我开源的轻量级电子签名组件——react-sign2. 今天继续和大家分享一下小伙伴极客恰恰 贡献的vue3版电子签名组件vue3-sign.

aff0295aa60fa35077847665634557f6.gif

我们可以使用它轻松的实现电子签名, 比如说常用的合同签字, 文稿签名, 艺术签名等, 并支持一键将签名保存为本地图片.

基本属性介绍

fdbcc6c34e6b7dff32a2abe60d15b35f.png

事件

e7ee34c74f1712dbd85b76bdbe257309.png

实现思路

按照笔者之前的习惯, 在设计组件之前都会先明确组件的设计需求, 然后根据健壮组件的设计原则来落地组件, 这里给大家分享一下我总结的几条组件设计经验:

  • 对组件进行严格的属性设计, 保证业务层能低成本使用组件, 并保持一定的可配性

  • 组件内外部类型约定(ts规范), 并提供对逻辑的兼容性

  • 可读性(代码格式统一清晰,注释完整,代码结构层次分明,编程范式使用得当)

  • 可用性(代码功能完整,在不同场景都能很好兼容,业务逻辑覆盖率)

  • 复用性(代码可以很好的被其他业务模块复用)

  • 可维护性(代码易于维护和扩展,并有一定的向下/向上兼容性)

  • 高性能(组件具有一定的性能, 如复杂场景的渲染, 计算等)

对于电子签名组件, 我们最小化的需求就是能满足用户的线上签名, 并能保存签名数据.

5c2355973a01b69ca84577b18b0e1428.png

实现代码

由于代码使用vue3实现, 这里主要分校一下核心js实现, 详细代码可以参考 github : https://github.com/open-vue3/vue3-sign.

<script lang="ts" setup>
import { ref, watch, onMounted,onUnmounted } from "vue";
interface IProps {
    /**
     * @description   画布宽度
     * @default       400
     */
     width?: number;
     /**
      * @description   画布高度
      * @default       200
      */
     height?: number;
     /**
      * @description   线宽
      * @default       4
     */
     lineWidth?: number;
     /**
      * @description   线段颜色
      * @default       'red'
     */
     strokeColor?: string;
     /**
      * @description   设置线条两端圆角
      * @default       'round'
     */
     lineCap?: string;
     /**
      * @description   线条交汇处圆角
      * @default       'round'
     */
     lineJoin?: string;
     /**
      * @description   画布背景颜色
      * @default       'transparent'
     */
     bgColor?: string;
     /**
      * @description   true
     */
     showBtn?: boolean;
     /**
     * @description   当保存时的回调, blob为生成的图片bob
     * @default       -
     */
     onSave?: (blob: Blob) => void;
    /**
     * @description   当画布清空时的回调, 参数为画布的上下文对象,可以直接使用canvas的api
     * @default       -
     */
     onClear?: (canvasContext: CanvasRenderingContext2D) => void;
     /**
     * @description   当画布结束时的回调
     * @default       -
     */
     onDrawEnd?: (canvas: HTMLCanvasElement) => void;
  }

const props = withDefaults(defineProps<IProps>(), {
  width: 400,
  height: 200,
  lineWidth:4,
  strokeColor:'green',
  lineCap:'round',
  lineJoin:'round',
  bgColor:'transparent',
  showBtn:true
});

const {
  width,
  height,
  lineWidth,
  strokeColor,
  lineCap,
  lineJoin,
  bgColor,
  showBtn,
  onSave,
  onClear,
  onDrawEnd
} = props;

   const canvasRef = ref<any>(null);
    const ctxRef = ref<any>(null);

   // 保存上次绘制的 坐标及偏移量
   const client = ref<any>({
              offsetX: 0, // 偏移量
              offsetY: 0,
              endX: 0, // 坐标
              endY: 0
          })
  

 // 判断是否为移动端
 const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent));

   // 取消-清空画布
   const cancel = () => {
    // 清空当前画布上的所有绘制内容
    if(canvasRef.value) {
      const canvasCtx = canvasRef.value.getContext("2d");
      canvasCtx.clearRect(0, 0, width, height);
      
      onClear && onClear(canvasRef.value)
    }
  }

  // 保存-将画布内容保存为图片
  const save = () => {
    // 将canvas上的内容转成blob流
    canvasRef.value.toBlob((blob: any) => {
        // 获取当前时间并转成字符串,用来当做文件名
        const date = Date.now().toString()
        // 创建一个 a 标签
        const a = document.createElement('a')
        // 设置 a 标签的下载文件名
        a.download = `${date}.png`
        // 设置 a 标签的跳转路径为 文件流地址
        a.href = URL.createObjectURL(blob)
        // 手动触发 a 标签的点击事件
        a.click()
        // 移除 a 标签
        a.remove()

        onSave && onSave(blob);
    })
  }

   // 绘制
   const draw = (event: { changedTouches?: any; pageX?: any; pageY?: any; }) => {
        // 获取当前坐标点位
        const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
        // 获取canvas 实例
        const canvas:HTMLCanvasElement = canvasRef.value as any;
        
        const { x, y } = canvas.getBoundingClientRect();
        // 修改最后一次绘制的坐标点
        client.value.endX = pageX
        client.value.endY = pageY
        // 根据坐标点位移动添加线条
        ctxRef.value.lineTo(pageX - x, pageY - y)

        // 绘制
        ctxRef.value .stroke()
    };

   // 初始化
   const init = (event: { changedTouches?: any; offsetX?: any; offsetY?: any; pageX?: any; pageY?: any; }) => {
        // 获取偏移量及坐标
        const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event;
        const canvas:HTMLCanvasElement = canvasRef.value as any;

        const { x, y } = canvas.getBoundingClientRect();
     


        client.value.offsetX = offsetX
        client.value.offsetY = offsetY
        client.value.endX = pageX
        client.value.endY = pageY

        // 清除以上一次 beginPath 之后的所有路径,进行绘制
        ctxRef.value.beginPath()
        // 根据配置文件设置相应配置
        ctxRef.value.lineWidth = lineWidth
        ctxRef.value.strokeStyle = strokeColor
        ctxRef.value.lineCap = lineCap
        ctxRef.value.lineJoin = lineJoin
        // 设置画线起始点位
        ctxRef.value.moveTo(client.value.endX - x, client.value.endY - y)
        // 监听 鼠标移动或手势移动
        window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
    };
  // 结束绘制
  const closeDraw = () => {
         console.log(ctxRef.value);
        // 结束绘制
        ctxRef.value.closePath()
        // 移除鼠标移动或手势移动监听器
        window.removeEventListener("mousemove", draw)
        onDrawEnd && onDrawEnd(canvasRef.current)
    };
  const initCanvas =()=>{
       // 获取canvas 实例
       const canvas:HTMLCanvasElement = canvasRef.value as any;
          // 设置宽高
          canvas.width = width;
          canvas.height = height;
          // 创建上下文
          const ctx:any = canvas.getContext('2d');
          ctxRef.value = ctx;
          // 设置填充背景色
          ctxRef.value.fillStyle = bgColor;
          // 绘制填充矩形
          ctxRef.value.fillRect(
              0, // x 轴起始绘制位置
              0, // y 轴起始绘制位置
              width, // 宽度
              height // 高度
          );
  }
  const  addEventListener=()=>{
     // 创建鼠标/手势按下监听器
     window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init);
      // 创建鼠标/手势 弹起/离开 监听器
    window.addEventListener(mobileStatus ? "touchend" : "mouseup", closeDraw);
    
  }
  const  removeEventListener=()=>{
     // 创建鼠标/手势按下监听器
     window.removeEventListener(mobileStatus ? "touchstart" : "mousedown", init);
      // 创建鼠标/手势 弹起/离开 监听器
    window.removeEventListener(mobileStatus ? "touchend" : "mouseup", closeDraw);
    
  }
  
const initEsign=()=>{
     initCanvas();
     addEventListener();
    
  }

  onMounted(() => {
    initEsign();
});

onUnmounted(()=>{
  removeEventListener();
});

</script>

后期规划

1f61b6f9ae2b8a1a49ec02c6d12d6b52.png

欢迎大家共建.

参考资料

  1. https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API

  2. https://juejin.cn/post/7174251833773752350


以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~。

913932f00bf8c02b450b68ed73873174.gif

从零搭建全栈可视化大屏制作平台V6.Dooring

从零设计可视化大屏搭建引擎

Dooring可视化搭建平台数据源设计剖析

可视化搭建的一些思考和实践

基于Koa + React + TS从零开发全栈文档编辑器(进阶实战

点个在看你最好看

猜你喜欢

转载自blog.csdn.net/KlausLily/article/details/129920177