用二次贝塞尔曲线动态拟合笔锋走向实现流畅手写签名,需统一处理鼠标/触摸事件、高dpi适配、抗锯齿设置及速度感应线宽;关键步骤包括坐标归一化、touchaction禁用、dpr缩放、linecap/linejoin设为round、三点一组贝塞尔拟合与点距过滤。

用 Canvas 实现流畅手写签名,关键不是连直线,而是用二次贝塞尔曲线动态拟合笔锋走向——每一段都以前一段的运动趋势“柔化”当前转折,让线条自然圆润、不断续。
事件监听与坐标归一化
必须同时支持鼠标和触摸设备,且所有坐标统一基于 clientX/clientY 计算,并转换为 Canvas 逻辑坐标:
- 监听
mousedown/touchstart(注意passive: false并调用preventDefault()阻止滚动) - 监听
mousemove/touchmove,持续采集点位 - 通过
canvas.getBoundingClientRect()获取画布在视口中的位置,再用clientX - rect.left算出相对于画布左上角的横坐标,纵坐标同理 - 禁用浏览器默认手势:
canvas.style.touchAction = 'none'
高 DPI 适配与抗锯齿设置
否则在高清屏上签名会模糊、发虚:
- 获取设备像素比:
const dpr = window.devicePixelRatio || 1 - 放大画布物理尺寸:
canvas.width = canvas.clientWidth * dpr;canvas.height = canvas.clientHeight * dpr - 保持绘制坐标与 CSS 像素一致:
ctx.scale(dpr, dpr) - 开启圆头圆角:
ctx.lineCap = 'round';ctx.lineJoin = 'round' - 启用平滑渲染:
ctx.imageSmoothingEnabled = true
贝塞尔拟合:三点一组动态生成平滑段
不直接用 lineTo,也不每两点都调 quadraticCurveTo。正确做法是:
- 记录上上个点
(prevX, prevY)、上一个点(lastX, lastY)、当前点(x, y) - 控制点取上一个点与当前点的中点:
cpX = (lastX + x) / 2,cpY = (lastY + y) / 2 - 从上上个点出发,调用
ctx.quadraticCurveTo(cpX, cpY, x, y) - 每次绘制前只调一次
beginPath(),起笔时用moveTo(prevX, prevY),后续只追加曲线
手感优化:线宽随速度变化 + 点距过滤
真实手写有轻重缓急,快则细、慢则粗,还能避免密集采样造成的卡顿:
- 记录每个点的时间戳,计算相邻点间位移距离与耗时,得出瞬时速度
speed = distance / deltaTime - 线宽设为:
ctx.lineWidth = Math.max(0.5, Math.min(3, 2.5 - speed * 0.3)) - 跳过位移过小的点(如
distance ),防止抖动噪点 - 松开时补一段微延伸收尾:终点向外偏移 10%,模拟提笔回锋
导出时用 canvas.toDataURL('image/png') 即可获得透明背景 PNG;清空重签记得用逻辑尺寸调 clearRect(0, 0, canvas.width, canvas.height)。不复杂但容易忽略:所有坐标必须统一使用 clientX/clientY。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











