移动端轻点触发 touchstart 后 300ms 再触发 click,导致重复执行;解决核心是让一次点击只触发一个回调:用 @touchstart.prevent 阻断 click,或通过 isMoved 判断点击/拖动,或兼容 PC 用 click 为主、touchstart 为辅并 preventDefault。

移动端 Touch 与 Click 事件同时绑定时,用户轻点一次,会先触发 touchstart(或 touchend),约 300ms 后再触发 click,造成逻辑重复执行——比如按钮连点两次、弹窗打开又关闭、表单重复提交。这不是 bug,而是浏览器为兼容双击缩放保留的默认行为。解决核心在于:**让一次物理点击只触发一个有效回调**。
用 @touchstart.prevent 阻断 click(最简方案)
Vue 中直接使用事件修饰符,无需手动调用 preventDefault():
- 在模板中给目标元素添加
@touchstart.prevent="handleTap",同时移除@click - 这样既保留了毫秒级响应(比 click 快 300ms),又彻底避免了 click 触发
- 注意:
.prevent必须作用于touchstart,不能加在touchend上,否则无效
区分点击与拖动,保留 click 语义(需拖拽场景)
当元素既要支持点击跳转,又要支持拖动(如悬浮球、滑块),就不能粗暴禁用 click。此时应通过行为判断:
- 在
touchstart中记录起始坐标,并初始化isMoved = false - 在
touchmove中计算位移,若超过 5px 就设isMoved = true - 在
touchend中检查isMoved:为false则视为点击,手动调用业务逻辑;为true则执行拖动逻辑
兼容 PC 与移动端的统一写法(渐进增强)
如果页面需同时运行在桌面和手机端,不能只依赖 touch 事件:
- 优先监听
click,它在所有平台都可用 - 额外监听
touchstart,并在回调中调用e.preventDefault()—— 这个操作对 PC 无影响,对移动端则能抑制后续 click - 或者用
if ('ontouchstart' in window)动态注册 touch 事件,PC 环境跳过
避开常见陷阱
以下做法容易引发新问题,需谨慎:
- 不要在
touchmove中调用preventDefault(),否则页面无法滚动 - 不要在
touchstart中阻止默认行为后,还保留@click,这会导致 click 失效且无回退 - 遮罩层关闭时,若立即移除 DOM,残留的 click 可能穿透到下层元素,建议加
setTimeout(..., 100)延迟清理

















