应优先使用 changedTouches[0] 获取触点,因其始终包含本次事件变化的触点;touches 可能为空导致报错,targetTouches 更安全(仅限当前元素),preventDefault() 需按场景慎用,滑动判定需结合位移与时间双阈值。

直接用 touchstart 和 touchmove 实现基础触摸交互不难,但真机上容易卡顿、误判、失灵——核心问题不在事件本身,而在坐标读取方式、默认行为控制和状态管理是否匹配真实手指行为。
为什么 touchmove 里要用 changedTouches 而不是 touches
在 touchend 或快速滑动中,touches 可能已为空(手指已离开),但 changedTouches 一定包含本次事件变化的触点。若在 touchmove 中错误地依赖 touches[0],当用户中途抬起一根手指(多点操作)或快速划过,touches.length 可能突变为 0,导致 Cannot read property 'clientX' of undefined 错误。
- 正确做法:始终优先用
event.changedTouches[0]获取当前触发该次事件的触点 - 仅当需要判断是否多点(如双指缩放)时,才查
event.touches.length -
targetTouches更安全——它只返回落在当前元素上的触点,避免被父容器或其他遮罩层干扰
preventDefault() 什么时候必须加,什么时候反而坏事
event.preventDefault() 不是“加了就灵”,它会直接切断浏览器原生滚动、缩放、长按菜单等行为。加错位置,轻则页面不能滚,重则 click 事件彻底失效。
- 必须加:当你明确接管了垂直/水平拖动(如轮播图、抽屉菜单),且希望阻止页面整体滚动时,在
touchmove回调开头立即调用 - 必须不加:按钮、链接等可点击区域,除非你同时实现了自定义点击逻辑;否则加了
preventDefault()后click永远不会触发 - 折中方案:用
{ passive: false }显式声明监听器可取消默认行为,避免 Chrome 控制台警告 “Unable to preventDefault inside passive event listener”
如何可靠判断一次“滑动”而不是“点击”
仅靠时间阈值(比如 >150ms)或位移阈值(比如 >10px)都不可靠——用户可能慢速长按后突然滑动,也可能快速点按但因屏幕响应延迟产生微小偏移。
立即学习“前端免费学习笔记(深入)”;
- 推荐组合判断:在
touchstart记录startX/startY和Date.now();在touchend中同时检查:Math.abs(endX - startX) > 30且Math.abs(endY - startY) (水平滑动),再结合 <code>duration (排除慢速拖拽) - 引入
isMoved标志位:在touchmove中只要发生任意位移就设为true;touchend时若仍为false,可安全视为点击并触发模拟 click 逻辑 - 务必在
touchend和touchcancel中重置所有缓存变量(startX、isMoved等),否则下次触摸会继承上次残留状态
CSS 的 touch-action 属性比 JS 更早介入手势控制
JS 的 touchstart/touchmove 是事件层处理,而 touch-action 是渲染层指令——它让浏览器在手指刚落下时就决定“这个区域我不管了”,从而避免 300ms 延迟、提升响应速度,也减少 JS 绑定负担。
- 对纯横向滑动容器(如轮播图):直接加
touch-action: pan-y none(允许纵向滚动,禁用横向默认行为) - 对列表项内带左右滑动操作(如滑动删除):用
touch-action: pan-up pan-down,保留上下滚动,把左右交给 JS 处理 - 绝对禁止在按钮上写
touch-action: none——这会让所有浏览器认为“此处无交互”,click和touchend都收不到
真正难的不是监听到触摸,而是区分“用户想滚动页面”“想拖动组件”还是“只是点一下”。这些边界情况往往只在真机快速滑动、多指误触、边缘滑出时暴露,别依赖模拟器测试。



















