为什么TouchEnd事件在快速滑动后会重复触发?

程序员奕森 阅读 46

我在做移动端滑动删除功能时,给元素绑定了touchstart和touchend事件。但发现当手指快速滑动后突然抬起,touchend会触发两次,导致删除逻辑执行两次。代码逻辑看起来没问题,但测试多次还是这样:


element.addEventListener('touchstart', (e) => {
  startX = e.touches[0].clientX;
});
element.addEventListener('touchend', (e) => {
  if (Math.abs(endX - startX) > 50) {
    console.log('执行删除'); // 这里会重复输出
  }
});

我尝试过加标志位防重,但滑动速度过快时标志位还没来得及重置。是不是touchend在某些情况下会被浏览器自动触发两次?或者需要结合touchcancel处理?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
FSD-玉茂
你这问题我见过不少次了,不是 touchend 真的触发两次,而是你没处理好滑动过程中可能触发的多个 touch 事件链。

问题出在:快速滑动时,手指可能在抬起前已经移出了目标元素范围,浏览器会先触发一次 touchend,紧接着又触发一次 touchcancel,而某些机型(特别是 iOS)在 touchcancel 后还会补发一次 touchend,所以你看到“两次执行”。

WP 里做移动端交互,别光盯 touchend,得把 touchcancel 也纳入考虑。而且你那个 endX 根本没赋值,代码里只有 startX,这逻辑本身就跑不通,估计是简化代码时漏了。

正确做法是:在 touchstart 里初始化状态,在 touchmove 里记录实时位置,在 touchend 和 touchcancel 里统一处理删除逻辑,并且用一个 flag 防重。注意 flag 要设在闭包里,别用全局变量,不然多元素场景直接崩。

下面这种写法在实际项目里跑过很多年了:

let deleting = false;
element.addEventListener('touchstart', (e) => {
if (deleting) return;
deleting = true;
startX = e.touches[0].clientX;
endX = startX;
});
element.addEventListener('touchmove', (e) => {
endX = e.touches[0].clientX;
});
element.addEventListener('touchend', handleDelete);
element.addEventListener('touchcancel', handleDelete);

function handleDelete() {
if (!deleting) return;
if (Math.abs(endX - startX) > 50) {
console.log('执行删除');
// 实际删除逻辑……
}
deleting = false;
}


重点是把 deleting 标志位在所有结束事件里统一重置,别在 touchend 单独处理完就不管了。另外别用 setTimeout 去延迟重置,速度一快就失效,闭包+同步 flag 才稳。

还有个坑:如果你用的是 jQuery 的 on('touchend'),那更麻烦,有些版本会把 touchcancel 也冒泡成 touchend,最好原生加事件监听,别偷懒。
点赞 8
2026-02-23 23:11
IT人子荧
这个问题主要是因为快速滑动时浏览器可能会触发多次touchend事件,建议结合标志位和时间戳来防抖。试试这个:

let isDeleting = false;
let lastTriggerTime = 0;

element.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
});

element.addEventListener('touchend', (e) => {
const now = Date.now();
if (isDeleting || now - lastTriggerTime < 300) return;
isDeleting = true;
lastTriggerTime = now;

if (Math.abs(e.changedTouches[0].clientX - startX) > 50) {
console.log('执行删除');
// 模拟异步操作后重置状态
setTimeout(() => isDeleting = false, 300);
} else {
isDeleting = false;
}
});


记得根据实际需求调整时间间隔,300毫秒是个参考值。如果还遇到问题,可能需要结合touchcancel一起处理。
点赞 4
2026-02-19 00:00