canvas手写签名功能的实现

最近遇到一个h5手写签名的需求,按理说这种功能网上随便一搜一大把现成的源码和组件,但是像这种比较经典又很简单的功能,还是要弄清楚到底怎么实现的。

图片

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

整理思路

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

  2. 2. 指定画笔的样式

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

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

<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",
        (e) => {
            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);

撤销和重写功能

整理思路

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

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

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

// 改写一下前面的代码
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
    );
};

在页面上加两个按钮

<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手写签名,并且实现了撤销和重写,以及导出为图片的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值