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手写签名的资料请关注脚本之家其它相关文章!

相关文章

  • 详解小程序中h5页面onShow实现及跨页面通信方案

    详解小程序中h5页面onShow实现及跨页面通信方案

    这篇文章主要介绍了小程序中h5页面onShow实现及跨页面通信方案,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • 项目中常用的JS方法整理

    项目中常用的JS方法整理

    这里给大家整理的是本人上个项目中所用到的js方法,都是些非常常用的javascript方法,相信小伙伴们也能经常用到,这里整理出来分享给大家。
    2015-01-01
  • vue iView 上传组件之手动上传功能

    vue iView 上传组件之手动上传功能

    iView 主要服务于 PC 界面的中后台业务,是一套高质量的开源 UI 组件库。这篇文章主要介绍了iView 上传组件之手动上传功能,需要的朋友可以参考下
    2018-03-03
  • js实现人才网站职位选择功能的方法

    js实现人才网站职位选择功能的方法

    这篇文章主要介绍了js实现人才网站职位选择功能的方法,涉及javascript动态操作页面元素结点的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • js select下拉联动 更具级联性!

    js select下拉联动 更具级联性!

    这篇文章主要为大家详细介绍了js select下拉联动的相关资料,更具级联性!文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • uniapp微信小程序多环境配置以及使用教程

    uniapp微信小程序多环境配置以及使用教程

    前后端分离开发模式中,无论前后端都有可能区分不同的环境配置,下面这篇文章主要给大家介绍了关于uniapp微信小程序多环境配置以及使用的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • 静态的动态续篇之来点XML

    静态的动态续篇之来点XML

    静态的动态续篇之来点XML...
    2006-08-08
  • 中文字符串截取的js函数代码

    中文字符串截取的js函数代码

    有时在显示某段文字的时候,可能会太长,影响我们页面的显示效果,如果仅是英文,那么我们可以用String.substring(start, end)函数就已经够用了,但是通常我们都会遇到既有英文,又有汉字的情况
    2013-04-04
  • undefined与null的区别示例详解

    undefined与null的区别示例详解

    这篇文章主要为大家介绍了undefined与null的区别示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • IE6下focus与blur错乱的解决方案

    IE6下focus与blur错乱的解决方案

    Miller同学发现的IE6 bug:如以下代码,点击textarea时,引发window的blur,导致focus与blur配对混乱
    2011-07-07

最新评论