如何通过canvas实现粗细不同的电子签名

发布时间 2023-04-24 17:43:17作者: 火星写程序

想要实现一个电子签名,可以支持鼠标签名,还能类似书法效果线条有粗有细,同时可以导出成图片.

一、实现连贯的划线

  1)首先需要注册鼠标下压、鼠标放开、鼠标移出和鼠标移动事件,通过鼠标下压赋值downFlag标记开始绘制

  2)  鼠标移动时,将当前坐标位置传入绘制方法,通过lineTo方法实现绘制

/**
     * 按下鼠标启动绘制标记
     **/
    canvas.addEventListener('mousedown', e => {
      preCoord = [e.offsetX, e.offsetY, new Date().getTime()];
      downFlag = true;
    })

    /**
     * 鼠标松开结束绘制
     **/
    canvas.addEventListener('mouseup', e => {
      downFlag = false;
    })
    canvas.addEventListener('mouseout', () => {
      downFlag = false;
    })

    /**
     * 鼠标移动时绘制文字
     **/
    canvas.addEventListener('mousemove', e => {
      if (downFlag) {
        const coord = [e.offsetX, e.offsetY];
        drawSign(coord);
        preCoord = [...coord, new Date().getTime()];
      }
    })

  3) 启动线的绘制,其中注释的线段

function drawSign(coord) {
      // 为了实现阶段性线的不同粗细程度,所以每次绘制必须重新开始一段路径
      ctx.beginPath();
      getColor(coord);

      ctx.lineTo(...preCoord);
      ctx.lineTo(...coord);

      ctx.stroke();
      ctx.closePath();
    }

 

二、为了美观,实现书法类似的效果,需要设每个线段设置不同的宽度才可以

  1)根据每两个事件点的时间差计算一个倍数关系,然后乘以一个基础宽度就可以得到不同的宽度,本文实现的效果是绘制越慢线条越宽,越快线条越窄

  2) 设置线的连接方式和线端点效果,使整个线条看起来更加圆滑

/**
     * 根据绘制时间差设置绘制线宽
     **/
    function getColor(coord) {
      if (preCoord.length === 0) {
        return;
      }
      // 当前是计算的每两个点的时间差是五毫秒的倍数
      const tempMulti = (new Date().getTime() - preCoord[2]) / 5;
      if (tempMulti > multi) {
        multi = multi * 1.4;
      } else {
        multi = multi * 0.9;
      }
      if (multi > 5) {
        multi = 5;
      }
      if (multi < 1) {
        multi = 1;
      }
      ctx.lineWidth = 2 * multi;
      
      // 通过设置连线效果和线端点效果可以使线条看起来更圆滑
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
      ctx.strokeStyle = 'rgba(153, 153, 153, 1)';
    }

三、下面奉上完整的代码和效果图

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>signature</title>
</head>
<body style="text-align: center;">
  <div>
    <button id="output" style="margin: 10px;">导出</button>
  </div>
  
  <canvas id="canvas" width="800" height="800" style="width: 800px;height: 800px;border: 1px solid gray;"></canvas>
</body>
</html>
<script type="module">
  window.onload = (() => {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 上一个绘制点的坐标
    let preCoord = [];
    // 绘制启动标志
    let downFlag = false;
    // 初始线宽
    let multi = 5;
    
    /**
     * 按下鼠标启动绘制标记
     **/
    canvas.addEventListener('mousedown', e => {
      preCoord = [e.offsetX, e.offsetY, new Date().getTime()];
      downFlag = true;
    })

    /**
     * 鼠标松开结束绘制
     **/
    canvas.addEventListener('mouseup', e => {
      downFlag = false;
    })
    canvas.addEventListener('mouseout', () => {
      downFlag = false;
    })

    /**
     * 鼠标移动时绘制文字
     **/
    canvas.addEventListener('mousemove', e => {
      if (downFlag) {
        const coord = [e.offsetX, e.offsetY];
        drawSign(coord);
        preCoord = [...coord, new Date().getTime()];
      }
    })

    document.getElementById('output').addEventListener('click', e => {
      const a = document.createElement('a');
      a.href = canvas.toDataURL('image/png');
      a.download = 'signature.png';
      a.click();
    })
    
    /**
     * 根据绘制时间差设置绘制线宽
     **/
    function getColor(coord) {
      if (preCoord.length === 0) {
        return;
      }
      // 当前是计算的每两个点的时间差是五毫秒的倍数
      const tempMulti = (new Date().getTime() - preCoord[2]) / 5;
      if (tempMulti > multi) {
        multi = multi * 1.4;
      } else {
        multi = multi * 0.9;
      }
      if (multi > 5) {
        multi = 5;
      }
      if (multi < 1) {
        multi = 1;
      }
      ctx.lineWidth = 2 * multi;
      
      // 通过设置连线效果和线端点效果可以使线条看起来更圆滑
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
      ctx.strokeStyle = 'rgba(153, 153, 153, 1)';
    }

    function drawSign(coord) {
      // 为了实现阶段性线的不同粗细程度,所以每次绘制必须重新开始一段路径
      ctx.beginPath();
      getColor(coord);

      ctx.lineTo(...preCoord);
      ctx.lineTo(...coord);

      ctx.stroke();
      ctx.closePath();
    }
  })
</script>
完整代码