3 分钟实现一个 Canvas 签名板,支持 PC 和移动端

在很多场景中我们需要用户在线签署,比如:在线合同、快递确认、表单签名等。 今天我们来实现一个简单的 Canvas 签名板 ,它支持 PC 鼠标绘制移动端触摸绘制

需求分析

  1. 实现签名功能。
  2. 既支持鼠标操作,也支持触摸操作。
  3. 支持修改线条宽度和颜色
  4. 支持导出 PNG、SVG、JSON、点坐标等格式

核心思路

  1. 使用 Canvas API 来绘制签名:

    • ctx.beginPath() 开始绘制路径。
    • ctx.moveTo(x, y) 移动画笔到起始点。
    • ctx.lineTo(x, y) 绘制到目标点。
    • ctx.stroke() 执行绘制指令。
  2. 监听鼠标和触摸事件,将用户的手势轨迹映射到 Canvas 上。

  3. 关键点:如何将鼠标/手指在屏幕上的位置,转换为 相对于 Canvas 的坐标

    • 使用 element.getBoundingClientRect() 获取 Canvas 的偏移量。

整体思维导图

实现步骤

1. 封装获取坐标值的工具类(兼容鼠标和触摸事件)

js 复制代码
function getCoordinates(e) {
    let x, y;
    if (e.type.includes('touch')) {
        const touch = e.touches[0] || e.changedTouches[0];
        const rect = canvas.getBoundingClientRect();
        x = touch.clientX - rect.left;
        y = touch.clientY - rect.top;
    } else {
        const rect = canvas.getBoundingClientRect();
        x = e.clientX - rect.left;
        y = e.clientY - rect.top;
    }
​
    return [x, y];
}

2. 创建 Canvas

html 复制代码
<canvas id="signatureCanvas"></canvas>

3. 监听鼠标事件

js 复制代码
// 鼠标按下
canvas.addEventListener('mousedown', startDrawing); // 开始绘制
// 鼠标移动
canvas.addEventListener('mousemove', draw); // 绘制线条
// 鼠标弹起
canvas.addEventListener('mouseup', stopDrawing); // 结束绘制
// 鼠标从元素上移出,防止用户将鼠标移出画布时意外中断绘制状态
canvas.addEventListener('mouseout', stopDrawing); // 结束绘制

4. 监听触摸事件(移动端)

js 复制代码
canvas.addEventListener('touchstart', startDrawing); // 开始绘制
canvas.addEventListener('touchmove', draw); // 绘制线条
canvas.addEventListener('touchend', stopDrawing); // 结束绘制

5. 开始绘制 startDrawing 具体实现

js 复制代码
function startDrawing(e) {
    isDrawing = true;
    [lastX, lastY] = getCoordinates(e);
​
    // 开始新路径
    currentPath = {
        points: [], // 点坐标列表
        color: strokeStyle, // 线条颜色
        width: lineWidth // 线条宽度
    };
}

6. 绘制中 draw 具体实现

js 复制代码
function draw(e) {
    if (!isDrawing) return;
​
    const [x, y] = getCoordinates(e);
​
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.strokeStyle = strokeStyle;
    ctx.lineWidth = lineWidth; // 设置的线条宽度
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.stroke();
​
    // 记录点
    if (currentPath) {
        currentPath.points.push({x, y});
    }
​
    [lastX, lastY] = [x, y];
}

7.结束绘制 stopDrawing 具体实现

js 复制代码
function stopDrawing() {
    if (!isDrawing) return;
​
    isDrawing = false;
​
    // 保存当前路径,便于后续撤销、重绘、导出等操作
    if (currentPath && currentPath.points.length > 0) {
        drawingData.push(currentPath);
    }
    currentPath = null;
}

8. 设置线条宽度

js 复制代码
function setWidth() {
    lineWidth = lineWidth < 10 ? lineWidth + 1 : 1;
    widthValue.textContent = lineWidth;
}

导出图片等功能

js 复制代码
function exportPNG() {
  const dataURL = canvas.toDataURL('image/png');
  console.log("PNG Base64:", dataURL);
  return dataURL;
}

总结

源码

通过简单的 Canvas API + 鼠标/触摸事件 ,我们就能实现一个跨平台的签名功能。 这类功能的关键在于 事件处理坐标转换,同时也为我们理解前端与用户交互提供了一个很好案例。

当然,实际项目中我们还可以做更多扩展,比如:

  • 增加 清空画布、撤销操作
  • 支持 保存为图片并上传服务器
  • 提供 不同颜色和笔刷粗细选择

这样,一个小小的 Canvas 签名板,就能变成实用的电子签署工具。

相关推荐
2501_920931704 小时前
React Native鸿蒙跨平台采用ScrollView的horizontal属性实现横向滚动实现特色游戏轮播和分类导航
javascript·react native·react.js·游戏·ecmascript·harmonyos
0思必得05 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东5166 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino6 小时前
图片、文件的预览
前端·javascript
2501_920931707 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman05288 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔8 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李8 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN8 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒8 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局