移动端签名问题根源在于事件链、坐标系、像素比未对齐:需禁用touchmove默认行为防中断,用getboundingclientrect动态算坐标防偏移,按devicepixelratio缩放canvas防模糊,并手动重置绘图状态防继承错误。

移动端签名轨迹断连、PC端坐标偏移、导出图片模糊——这些问题不是配置没写对,而是事件链、坐标系、像素比三者没对齐。直接上可运行的关键点。
为什么 touchstart/touchmove 必须禁用默认行为
移动端 Safari 和 Android WebView 默认把 touchmove 当作滚动或缩放手势,一旦不调 e.preventDefault(),手指一划就触发页面滚动,笔迹直接中断或跳点。这不是“偶尔失灵”,是浏览器策略级拦截。
- 只对目标
canvas元素调用preventDefault(),别在 document 级阻止,否则影响其他交互 -
touchstart阶段就要调一次preventDefault(),否则 iOS 可能拒绝后续touchmove事件 - 别混用
mousedown和touchstart在同一个监听器里——部分安卓 WebView 会丢事件
canvas 坐标怎么算才不偏移
直接用 event.clientX - canvas.offsetLeft 是错的:它忽略滚动、CSS 缩放、父容器 transform,尤其在微信内嵌浏览器里必出错。
- 每次都要动态调
canvas.getBoundingClientRect(),不能缓存结果 - 移动端必须用
e.touches[0].clientX(不是changedTouches),多指时也只取第一个点 - 如果
canvas有 CSSwidth/height(比如style="width: 400px; height: 200px;"),得按比例缩放:const scaleX = canvas.width / rect.width,再算x = (e.clientX - rect.left) * scaleX
高清屏签名发虚、线条像马赛克
根本原因是没适配 window.devicePixelRatio。Canvas 的 width/height 属性是逻辑像素,但绘制时按物理像素渲染,1x 屏幕上 1px 线条在 2x 屏就是半像素,浏览器自动插值导致模糊。
- 初始化时读
const dpr = window.devicePixelRatio || 1 - 设
canvas.width = canvas.clientWidth * dpr,canvas.height = canvas.clientHeight * dpr - 用 CSS 把显示尺寸缩回:
canvas.style.width = canvas.clientWidth + 'px' - 最后调
ctx.scale(dpr, dpr),让所有绘图坐标自动匹配 - 清空画布前务必
ctx.setTransform(1, 0, 0, 1, 0, 0),否则缩放残留会导致新笔画变形
toDataURL 导出空白或超长,toBlob 又不触发回调
toDataURL 返回的是带前缀的完整 data URL 字符串,后端解码时若没截掉 data:image/png;base64, 会失败;而 toBlob 在某些旧版安卓 WebView 中不支持 Promise 版本,回调不执行。
- 导出前确认
pointerup或touchend已触发,避免异步未完成就调用 - 优先用
canvas.toBlob(callback, 'image/png', 0.8),第三个参数控制压缩质量,0.8 是清晰与体积的平衡点 - 若必须用 base64,服务端接收前先校验字符串长度(>2MB 就拒收)和字符集(只含 A-Z a-z 0-9 + / =)
- 别依赖
canvas.toDataURL()后立即显示缩略图——有些浏览器会延迟渲染,加个setTimeout(() => {}, 0)再读取更稳
最易被忽略的是:每次清空画布后,ctx.lineWidth、ctx.strokeStyle、ctx.lineCap 这些状态不会自动重置,下一笔签名可能继承上一次的粗细或颜色。重签前手动设一遍比靠初始化更可靠。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











