canvas在线签名插件

发呆

关于

原文地址:canvas在线签名插件
插件地址github:https://github.com/javascript-wei/canvas-sign
体验预览:canvas-sign

背景

最近临时接到一个需求,需要客户方在线签字然后保存到服务器,功能大致有撤销(也就是笔画的每一笔都需要支持撤销)、更改签字颜色、移动端和pc端都支持。因为紧急需求,有现成的插件当然是最好的,毕竟学会偷懒也是一门技巧(滑稽),临时找了一个jSignature第三方的签名插件,仔细研究了一下发现该插件依赖复杂,使用该插件还需要引入jQuery,因为整个项目都能没有引入jquery,插件源码也是ES5,甚至还是ES3,当时我就犹豫了,不能因为一个签名引入这么复杂的插件吧。
因为本次需求最终就只需要一张图片保存到后台,也不需数字证书验证部分之类的,对于喜欢动手(完全是被逼)的我来说,这完全可以自己写啊。因为需求是周五出的,然后下周二就要,看来周末又是一个悄悄加班的日子。

分析

签名是若干操作的集合,起于用户手写姓名,终止于签名图片上传,中间还包含图片的处理,比如说减少锯齿、撤销、预览等,除了canvas绝无二选。

手撕

从整个交互上看,开始绘制时候需要定义起始点touchstart(移动端开始)、mousedown(pc鼠标按下),为了完成绘制,还需要处理手指移动或鼠标移动,监听处理两个事件touchmove(移动端)、mousemove(PC端)。

       const handleMove = (e) => {
            this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
        }
        const handleDown = (e) => {
            this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
        }
        constf fn = {
            mousedown: handleDown,
            mousemove: handleMove,
            //移动端
            touchmove: handleMove,
            touchstart:handleDown,
        }

画线

接下来划线部分,canvas原生有以下api提供画线:

  1. 开始路径(beginPath)
  2. 定位起点(moveTo)
  3. 移动画笔(lineTo)
  4. 绘制路径(stroke)

有了start和move事件,画线的思路就明确了很多,按下按钮时候我们需要重置画笔beginPath,并且移动画笔moveTo,因为按下即便不移动也算是一个完整的绘制过程:

    const handleDown = (e) => {
        //按下键时候重置画笔
        this.ctx.beginPath();
        //isMouseDown 判断是否按下按键已经就绪绘制
        this.isMouseDown = true;
        const position = { x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 };
        this.ctx.moveTo(position.x,position.y)
        this.creat({ x: position.x, y: position.y });
    }

移动处理:

  const handleMove = (e) => {
        //判断pc端是否鼠标左键按下,移动端不需要做判断
        if ((!this.isMouseDown || e.which != 1) && !mobile) return;
        e = mobile ? e.touches[0] : e;
        this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
    }
   const creat = (position = { x, y })=> {
         ctx.lineTo(position.x, position.y);
         ctx.stroke();
    }

以上代码中的left和top并非内置变量,它们分别表示着画布距屏幕左边和顶部的像素距离,主要用于将屏幕坐标点转换为画布坐标点。以下是一种获取方法:

const { left, top } = this.canvas.getBoundingClientRect();

设置

接下来是更改画笔设置,需要支持原生canvas画笔属性:

    // 提供一个条件是否更改保存当前样式,用于导入json时避免和已有的ctx样式冲突
    setLineStyle(style = {},isSaveLineStyle = true) {
        const ctx = this.ctx;
        const lineStyle =  isObject(style)? { ...this.lineStyle, ...style } : this.lineStyle;
        if(isSaveLineStyle){
            this.lineStyle = lineStyle;
        }
        Object.keys(lineStyle).forEach(key => {
            ctx[key] = lineStyle[key];
        });
        ctx.beginPath();
        return this;
    }

导出json

画线需要路径和画笔的配置,路径则一系列坐标的集合,而画笔配置则是setLineStyle时传入的各个属性。由此可以得到json数据结构:

    const json = [{
        lineStyle:{},
        position:[]
    }]

什么时候保存,从开始绘制到结束绘制处理json的情况有三种:开始绘制,绘制中,结束绘制。
开始绘制:鼠标按下时,也就是一个点,此时往json数组中psuh当前值。
绘制中:往数组的最后一条数据中的position添加移动点的信息。
结束绘制:不在往数组添加数据,此时保存当前的lineStyle的信息。

    setJson(value, type) {                                           
        const dataJson = this.dataJson;              
        const jsonLength = dataJson.length - 1;    
        switch (type) {                            
            case 'moving':                             
                dataJson[jsonLength].position.push(value); 
                break;                                     
            case 'end':                                
                dataJson[jsonLength].lineStyle = value;    
                break;                                    
            case "start":                              
                dataJson.push(value);                      
                break;                                     
            }                                          
     }

优化

为了让move更加流畅,可以考虑requestAnimationFrame优化:

   const raf = window.requestAnimationFrame;
   const move = raf?(e)=>{
        raf(() => {
            handleEvent.handleMove(e);
          });
    }:handleEvent.handleMove;

写到最后

本文介绍了在线签字实现的主要过程,核心代码可以在这里找到:canvas-sign
本问就讨论这么多内容,大家有什么问题或好的想法欢迎在下方参与留言和评论.

阅读 140
1 声望
0 粉丝
0 条评论
你知道吗?

1 声望
0 粉丝
宣传栏