移动端答题应用中touch事件早于新按钮加载触发导致双击跳题的异常
问题核心成因
这个bug是移动端场景下非常典型的触摸事件穿透问题,和代码实现逻辑直接相关:
- 移动端浏览器会把用户单次触摸的屏幕坐标暂存到事件队列中。当你通过同步逻辑清空旧按钮、批量插入新按钮完成DOM替换时,如果新按钮的布局位置和之前暂存的触摸坐标重合,浏览器会直接把这次暂存的触摸事件派发给位置匹配的新按钮,不需要用户再次触碰屏幕——这就是你观测到“新按钮还没完成页面渲染就被触发事件”的根本原因
- 你在
touchend回调中写的sleep(1000)是同步阻塞代码,会冻结JS执行线程,拉长DOM替换、浏览器重绘的时间窗口,反而让穿透事件有更高概率命中新按钮,进一步加剧双击跳题的问题 - 当前代码没有设置作答状态拦截,所有绑定到按钮的触摸事件只要触发就会立即执行判题逻辑,没有办法区分“用户主动触发”和“穿透缓存触发”的事件来源
修复方案
- 增加全局作答状态锁:
定义全局变量isAnswering,初始值为false。所有绑定到按钮的触摸事件回调开头,先判断该状态值,如果为true直接终止逻辑执行,不触发任何判题流程。在touchend触发的第一时间将状态设为true,等完成答题判定、新题目的所有按钮插入DOM、通过requestAnimationFrame确认浏览器完成整页重绘后,再将状态重置为false,彻底锁死事件穿透的触发窗口 - 移除
touchend回调内的同步sleep(1000)逻辑:同步阻塞会冻结页面所有交互响应,用户在阻塞期间的触碰操作会全部缓存成待触发事件,等阻塞结束后批量触发,会大幅提高重复判题、意外跳题的概率 - 优化事件绑定逻辑:
- 事件回调开头增加
event.preventDefault(),阻止移动端默认的300ms延迟点击、双击缩放行为,从根源减少穿透事件的触发 - 不再通过手写闭包硬传
answers[i]、btnID参数,改成创建按钮时把选项值、按钮id存到按钮的自定义属性上,事件触发时直接从event.currentTarget上读取对应值,避免闭包上下文错乱带来的逻辑异常
- 事件回调开头增加
- 兜底拦截:在清空按钮容器内容之前,先给容器添加100ms的临时事件拦截,这段窗口内所有指向容器的触摸事件直接终止派发,避免穿透事件命中刚生成的新按钮
内容的提问来源于stack exchange,提问作者Aserus
相关产品推荐
相关产品推荐

