当前位置: 当前位置:首页 > 热点 > vue手写签名正文

vue手写签名

作者:知识 来源:时尚 浏览: 【大 中 小】 发布时间:2026-10-17 06:06:13 评论数:

去年做一个合同签署系统,写签需求方甩过来一句话:“让用户在手机上用手指头画名字,写签画完能存成图片。写签”我当时想,写签这不就是写签 canvas 那点事吗。结果真动手,写签坑比想象的写签多——手指画出来的线是断的,笔迹粗细忽大忽小,写签保存出来的写签图糊得像马赛克。折腾了两天,写签才算把这块弄利索。写签下面就把我踩过的写签路直接摊开说。

核心其实就一个 <canvas> 元素,写签加上几个触摸事件。写签但别急着写代码,写签先想清楚一件事:签名要的是“笔迹”,不是“画笔”。鼠标和手指的坐标是离散的点,如果只是把每次 touchmove 的点连成线,快速滑动时点之间间隔很大,画出来就是一串断开的圆点。得用 lineTo 把这些点串起来,而且要在 beginPath 和 stroke 之间处理好路径的连续性。

先看基础结构。一个 canvas,宽高设成容器的实际像素,别用 CSS 拉伸,否则坐标全乱。我习惯用 devicePixelRatio 把画布放大再缩回来,这样在高清屏上线条才不会发虚:

<canvas id="sign" style="width:100%;height:300px;touch-action:none;"></canvas> 

注意那个 touch-action: none;,不加的话手指在 canvas 上滑动会触发页面滚动,签名直接变成翻页。这个属性比 preventDefault 更干净,浏览器层面就把手势接管了。

接下来是事件绑定。移动端要同时处理 touchstart、touchmove、touchend,PC 端还得兼容鼠标。我写了个统一的取点函数,把 event.touches[0] 和 event 的坐标归一化:

function getPos(e) {  const rect = canvas.getBoundingClientRect(); const touch = e.touches ? e.touches[0] : e; return {  x: touch.clientX - rect.left, y: touch.clientY - rect.top }; } 

这里有个细节:getBoundingClientRect() 每次都要重新算,因为页面可能滚动了。别图省事缓存 rect,滚动后坐标会偏。

画线的逻辑我用了“两点之间补中间点”的笨办法。手指移动快的时候,两个 touchmove 事件之间可能隔了十几个像素,直接 lineTo 会看到折线感。我的做法是在两点之间按固定步长插值,让线条平滑:

function drawLine(from, to) {  const dist = Math.hypot(to.x - from.x, to.y - from.y); const steps = Math.max(1, Math.floor(dist / 2)); ctx.beginPath(); ctx.moveTo(from.x, from.y); for (let i = 1; i <= steps; i++) {  const t = i / steps; ctx.lineTo( from.x + (to.x - from.x) * t, from.y + (to.y - from.y) * t ); } ctx.stroke(); } 

笔迹的质感靠 lineWidth 和 lineCap、lineJoin 调。我设成 3px、圆头、圆角,看起来像中性笔。别用默认的 butt 和 miter,拐角处会尖得扎眼。颜色用深蓝黑 #1a1a2e,比纯黑柔和,扫描件的感觉更真。

清空和保存是两个按钮的事。清空直接 ctx.clearRect,简单。保存稍微绕一下——canvas.toDataURL('image/png') 拿到 base64,但这时候背景是透明的,贴到白底合同上会变成黑糊糊一片。所以我在初始化时先给整个画布填一层白色:

ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); 

这样导出的 PNG 就是白底黑字,直接扔给后端存文件或者塞进 PDF 都行。如果后端要的是二进制,把 base64 的 data:image/png;base64, 前缀切掉再转 Blob。

还有个容易忽略的点:手指离开画布再按下去,如果直接 lineTo 会把上一笔的终点和这一笔的起点连起来,出现一道莫名其妙的横线。解决办法是维护一个 isDrawing 标志,touchstart 时设为 true 并记录起点,touchend 时设为 false,touchmove 里判断只有 true 才画。

最后说性能。签名区域不大,不用做节流,但 touchmove 触发频率很高,每帧都 stroke 一次在低端安卓机上会卡。我的做法是把点攒起来,用 requestAnimationFrame 批量画。实测下来,比每次事件都画流畅不少,笔迹也没延迟感。

整套代码不到 150 行,核心就是坐标转换、路径插值、白底导出这三块。写的时候别被“手写”两个字唬住,它本质就是个画板,只是手指比鼠标粗,容错要高一点。真机测试比模拟器靠谱,尤其是 iOS 的 Safari,对触摸事件的处理和安卓 Chrome 有细微差别,多试几台机器再交付。