关键是以三点为一组动态拟合平滑笔锋:用上上个点为起点、上下两点中点为控制点、当前点为终点调用quadraticcurveto,配合高dpi适配、多端事件监听及速度感知线宽优化。

用 Canvas 的二次贝塞尔曲线(quadraticCurveTo)画手写签名,关键不是“单点描迹”,而是“以三点为一组动态拟合平滑笔锋”——起点、控制点、终点需实时计算,让连续笔段自然衔接,避免生硬折角或断续感。
1. 基础结构:监听鼠标/触摸事件并初始化 Canvas
签名区域需同时兼容桌面(mousedown/mousemove/mouseup)和移动端(touchstart/touchmove/touchend)。Canvas 设置为高 DPI 适配,防止模糊:
- 获取 canvas 元素后,用
canvas.width = canvas.clientWidth * window.devicePixelRatio等比例放大画布逻辑尺寸 - 设置
ctx.scale(devicePixelRatio, devicePixelRatio)保持绘制坐标与 CSS 像素一致 - 禁用浏览器默认的长按选中、缩放行为(
touch-action: none)
2. 核心策略:用前一个点作控制点,当前点作终点,形成连续二阶贝塞尔链
不直接连直线,也不每两点都调用 quadraticCurveTo。正确做法是:
- 记录上一次落笔位置(
lastX,lastY)和当前移动点(currentX,currentY) - 取二者中点作为控制点:
cpX = (lastX + currentX) / 2,cpY = (lastY + currentY) / 2 - 从上上个点(
prevX,prevY)出发,用该中点作控制点,当前点作终点调用quadraticCurveTo(cpX, cpY, currentX, currentY) - 这样每一段都以前一段的走向“柔化”当前转向,视觉上笔迹圆润不断开
3. 优化手感:添加速度感知的线宽与点距过滤
真实手写有轻重缓急。可基于相邻点时间差和位移距离动态调整:
- 计算点间速度:
speed = distance / deltaTime,线宽设为Math.max(0.5, Math.min(3, 2.5 - speed * 0.3))(快则细,慢则粗) - 跳过过密采样点(如位移
- 起笔时用
beginPath()+moveTo();移动中只调quadraticCurveTo(),不重复beginPath()
4. 收尾与导出:平滑收笔 + 生成签名图像
松开鼠标/手指时,补最后一段自然收尾,并支持导出:
- 在
mouseup/touchend中再调一次quadraticCurveTo,控制点仍用最后两采样点中点,终点微调向外延伸 10% 模拟提笔回锋 - 导出 PNG:调用
canvas.toDataURL('image/png');如需透明背景,确保 canvas 未被其他样式污染(如 border 或 background) - 清空重签:用
ctx.clearRect(0, 0, canvas.width, canvas.height),注意传入的是逻辑尺寸(已乘 DPR)
不复杂但容易忽略:所有坐标必须统一使用 clientX/clientY 并减去 canvas 左上角偏移(getBoundingClientRect()),且全程避免混用 CSS 像素与设备像素。做好这几点,签名线条就能真正“跟手”又“有笔锋”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











