JavaScript使用canvas实现手写签名功能

 更新时间:2023年08月22日 14:04:10   作者:xintianyou  
最近遇到一个h5手写签名的需求,按理说这种功能网上随便一搜一大把现成的源码和组件,但是像这种比较经典又很简单的功能,还是要弄清楚到底怎么实现的,所以接下来本文就给大家介绍一下如何用canvas实现手写签名功能

预览效果

如果不想阅读文章,可直接查看源码

先实现基本需求(能签名即可)

整理思路

  • 准备一个canvas画布,得到context对象

  • 指定画笔的样式

  • 监听鼠标 / 手指的移动,得到每一次移动在画布上的坐标点,记录下来

  • 将这些点绘制到画布上形成线条

<canvas width="600" height="400" id="canvas" style="background-color: #ddd;"></canvas>

为了方便调试,我们本次仅演示pc端的操作。移动端思路是一样的,只不过监听的API不同。

常见的操作方式是:当鼠标左键按下的时候在画布上移动鼠标,就可以绘制。没有按下鼠标时,不管它。

注释很重要,我尽量写得很详细

window.onload = function () {    
    // 默认鼠标是没有按下的    
    let isDown = false;    
    // 记录上一次鼠标的位置    
    let lastX = 0; // x轴    
    let lastY = 0; // y轴        
    // 获取canvas
    const canvas = document.getElementById("canvas");    
    // 获取canvas的上下文    
    const ctx = canvas.getContext("2d");    
    // 定义线条的宽度,即画笔的粗细    
    ctx.lineWidth = 3;    
    // 定义画笔的颜色    
    ctx.strokeStyle = "#000";    
    /**
      * 定义绘制方法
      * 线条其实是由两个点连起来的一个线段
      * 一个又一个的小线段,连起来就组成了一个线条
      * 在画布上绘制线条,主要用到的三个核心方法      
      * moveTo: 是 Canvas 2D API 将一个新的子路径的起始点移动到 (x,y) 坐标的法。   
      * lineTo: 是 Canvas 2D API 使用直线连接子路径的终点到 x,y 坐标的法。      
      * 当然,定义了起点和终点还不够,还需要手动调用开始绘制这个路径
      * startX 和 startY 一起组成了起点的坐标
      * endX 和 endY 一起组成了线段终点的坐标
      */
    function draw(startX, startY, endX, endY) {        
        // 起点        
        ctx.moveTo(startX, startY);        
        // 终点        
        ctx.lineTo(endX, endY);        
        // 调用 stroke,即可看到绘制的线条        
        ctx.stroke();    
    }    
    // 监听鼠标按下,得到按下时鼠标在画布上的坐标    
    canvas.addEventListener("mousedown", ({ x, y }) => {
        isDown = true;
        // 按下时的点作为起点
        lastX = x;
        lastY = y;
        // 创建一个新的路径
        ctx.beginPath();
    }, false);    
    // 监听鼠标移动    
    canvas.addEventListener("mousemove", ({ x, y }) => {            
      // 没有按下就不管
      if (!isDown) return;
      // 调用绘制方法
      draw(lastX, lastY, x, y);
      // 把当前移动时的坐标作为下一次的绘制路径的起点
      lastX = x;
      lastY = y;
    }, false);
    // 监听鼠标抬起
    canvas.addEventListener("mouseup", () => {
      isDown = false;
      // 关闭路径
      ctx.closePath();
    }, false);
    // 监听鼠标移出
    canvas.addEventListener("mouseleave", () => {
        // 移出canvas范围,也认为是鼠标抬起了,再移入需要重新按下鼠标,避免移出之后再抬起鼠标,重新进入画笔还能继续画的问题
        isDown = false;
        ctx.closePath();
    }, false);
};

以上代码就实现了最基本的签名功能。

将canvas导出为图片

这个功能比较简单,思路都在注释里了

// 使用canvas的toDataURL()方法,将画布内容转换为base64格式的图片数据:
let imgData = canvas.toDataURL('image/png'); 
// 创建下载链接
let link = document.createElement('a');
link.download = 'picture.png';
link.href = imgData;
// 触发点击
link.click();
// 移除元素
document.body.removeChild(link);

撤销和重写功能

整理思路

  • 要实现撤销笔画回到上一步,就要知道上一步画了什么,就是要记录下来,我们可以用一个数组,把每次鼠标移动时得到的坐标放进去。

  • 通过基础功能我们知道了,画布上签名,是由多个线条组成的,而线条是由很多个点组成的。那我们撤销的时候,是撤销一条线,即一个笔画,而不是一个点。

  • 那么,怎么知道哪些点是属于一个笔画的呢,就是要给这些点分组,一个笔画为一组。我们规定,从鼠标按下到鼠标抬起,这之间移动时产生的所有点为一组,即一个笔画。用代码表示,就是有多个数组,所以我们定一个二维数组来保存所有的点。

改写一下前面的代码

window.onload = function () {    
    // 默认鼠标是没有按下的    
    let isDown = false;    
    // // 记录上一次鼠标的位置    
    // let lastX = 0; // x轴    
    // let lastY = 0; // y轴      
    // 这次要用数组来记录    
    let points = []; // 这是一个笔画的点    
    let allPonits = []; // 这是所有笔画的点        
    // 获取canvas元素     
    const canvas = document.getElementById("canvas");    // 获取canvas的上下文    
    const ctx = canvas.getContext("2d");    // 定义线条的宽度,即画笔的粗细    
    ctx.lineWidth = 3;    // 定义画笔的颜色    
    ctx.strokeStyle = "#000";        
    function draw(startX, startY, endX, endY) {        
        // 起点        
        ctx.moveTo(startX, startY);        
        // 终点        
        ctx.lineTo(endX, endY);        
        // 调用 stroke,即可看到绘制的线条        
        ctx.stroke();    
    }    
    // 监听鼠标按下,得到按下时鼠标在画布上的坐标    
    canvas.addEventListener("mousedown", ({ x, y }) => {            
        isDown = true;            
        // lastX = x;
        // lastY = y;
        // 保存当前坐标作为起点
        points.push({ x, y });            
        // 创建一个新的路径            
        ctx.beginPath();        
     }, false);    
     // 监听鼠标移动    
     canvas.addEventListener("mousemove", ({ x, y }) => {
         // 没有按下就不管
         if (!isDown) return;
         // 调用绘制方法
         // draw(lastX, lastY, x, y);
         // 把当前移动时的坐标作为下一次的绘制路径的起点            
         // lastX = x;
         // lastY = y;
         // 每次都取最后一个点,作为绘制的起点
         const lastPoint = points.at(-1);
         draw(lastPoint.x, lastPoint.y, x, y);
         // 把当前的点保存起来,又作为下一次绘制的起点
         points.push({ x, y });
     }, false);
     // 监听鼠标抬起    
     canvas.addEventListener("mouseup", (e) => {            
         isDown = false;
         // 关闭路径
         ctx.closePath();
         // 鼠标抬起,说明当前这一笔就结束了,把这一笔的所有点的数组放到总的里面
         allPonits.push(points);
         // 清空这一笔画,为下一笔画做准备            
         points = [];
     }, false);
    // 监听鼠标移出
    canvas.addEventListener("mouseleave", () => {
        // 移出canvas范围,也认为是鼠标抬起了,再移入需要重新按下鼠标,避免移出之后再抬起鼠标,重新进入画笔还能继续画的问题
        isDown = false;
        // 关闭画笔
        ctx.closePath();
        // 如果是先抬起鼠标再移出,那么points里面为空,就不保存了
        // 如果是先移出范围,移出时就保存,这样也不会触发上面的监听鼠标抬起事件,也不会push。
        if (points.length) {
            allPonits.push(points);
        }
        // 移出时也清空,因为无法判断是先抬起还是先移出的。
        points = [];
    }, false);
 };

在页面上加两个按钮

<div>
  <button id="prev">上一步</button>
  <button id="reset">重写</button>
</div>
const prev = document.getElementById("prev");
const reset = document.getElementById("reset"); 
// 清空画布
function resetPath() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// 上一步
prev.addEventListener("click", (e) => {
    // canvas本身不会记录用户的每一步操作
    // 要回到上一步,只能一次性清空所有的
    resetPath();
    // 删除最后一个笔画
    allPonits.pop();
    // 遍历所有的笔画并重新绘制
    // allPoints 是个二维数组
    allPonits.forEach((ps) => {
        ps.forEach((item, index) => {
          // 下一个坐标点
          let next = ps[index + 1];
          if (next) {
            // 有下一个点才执行,否则到最后一个会报错
            // 开始重新绘制
            ctx.beginPath();
            draw(item.x, item.y, next.x, next.y);
            ctx.closePath();
          }
       });  
    });
});
// 重写
reset.addEventListener("click", () => {
    // 点击重写时清空画布,并清空所有的点    
    resetPath();
    allPonits = [];
}, false);

到这里,我们就完成了canvas手写签名,并且实现了撤销和重写,以及导出为图片的功能。

以上就是JavaScript使用canvas实现手写签名功能的详细内容,更多关于JavaScript canvas手写签名的资料请关注脚本之家其它相关文章!

相关文章

最新评论