touchstart事件在移动端开发中的那些坑和解决方案
我的写法,亲测靠谱
touchstart 这玩意儿,看起来简单,实际用起来各种坑。我现在一般都这么处理:
function createTouchHandler(element) {
let startX, startY;
element.addEventListener('touchstart', function(e) {
// 防止默认行为,避免页面滚动
e.preventDefault();
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
});
element.addEventListener('touchend', function(e) {
const touch = e.changedTouches[0];
const endX = touch.clientX;
const endY = touch.clientY;
const deltaX = endX - startX;
const deltaY = endY - startY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// 只有滑动距离大于阈值才算有效滑动
if (distance > 10) {
handleSwipe(deltaX, deltaY);
} else {
handleClick();
}
});
}
function handleSwipe(deltaX, deltaY) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (deltaX > 0) {
console.log('向右滑动');
} else {
console.log('向左滑动');
}
} else {
// 垂直滑动
if (deltaY > 0) {
console.log('向下滑动');
} else {
console.log('向上滑动');
}
}
}
function handleClick() {
console.log('点击事件');
}
这种写法的好处很明显:既能处理滑动,又能处理点击,而且不会有冲突。我踩过太多坑了,之前的写法要么滑动时触发点击,要么点击没反应,反正各种诡异。
关键点在于那个 10像素的阈值,小于这个距离算点击,大于算滑动。这个数值可以根据实际情况调整,太小了容易误触,太大了影响体验。
这几种错误写法,别再踩坑了
最常见的错误就是直接用 touchstart 做点击判断:
// 错误写法1:直接用touchstart做点击
element.addEventListener('touchstart', function(e) {
// 这样写会有问题,每次触摸都会触发
doSomething();
});
这种写法的问题在于,用户手指刚碰到屏幕就执行了,根本没法滑动,体验差得一批。而且在需要滑动的场景下,基本废了。
还有一种常见错误是只监听 touchstart,不处理 touchend:
// 错误写法2:只监听touchstart
let isTouching = false;
element.addEventListener('touchstart', function(e) {
isTouching = true;
startX = e.touches[0].clientX;
});
// 缺少touchend处理,状态无法重置
这种写法会导致状态混乱,第一次触摸正常,第二次就开始抽风了。因为 start 状态没有重置,后续的计算都是错的。
还有人喜欢在 touchstart 里阻止所有默认行为:
// 错误写法3:盲目阻止默认行为
element.addEventListener('touchstart', function(e) {
e.preventDefault(); // 这样会阻止页面滚动,用户体验不好
// 其他逻辑...
});
这样写在需要滚动的页面里会造成问题,用户想滚动页面却滚不了,只能点击。应该根据具体需求来决定是否阻止默认行为。
实际项目中的坑
之前做电商项目的商品列表,需要支持左右滑动切换商品,同时保留点击进入详情页的功能。我一开始用 touchstart 直接触发点击,结果用户稍微拖动一下就跳转了,体验极差。
后来改成上面那种方案,先记录坐标,touchend 的时候判断移动距离,才算解决问题。但这里还有一个坑需要注意:多指触控的问题。
用户如果用两根手指点击,touches 数组会有两个元素,如果取错了就会出问题:
// 注意要取第一个touch
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
另外,touchend 事件里的 touches 可能为空,需要用 changedTouches:
// touchend时应该用changedTouches
const touch = e.changedTouches[0];
这个我也是调试了半天才发现的,当时在真机测试,touchend 的 touches 居然真的是空的。
还有个坑是快速连续点击,如果不做防抖处理,可能会触发多次事件:
let lastClickTime = 0;
function handleClick() {
const now = Date.now();
if (now - lastClickTime < 300) {
return; // 防止快速点击
}
lastClickTime = now;
// 执行点击逻辑
}
这个时间间隔可以根据需要调整,一般 300ms 左右比较合适。
性能优化的一些想法
对于大量元素绑定 touch 事件的情况,建议用事件委托:
document.addEventListener('touchstart', function(e) {
if (e.target.classList.contains('swipe-item')) {
// 处理具体的滑动逻辑
handleItemTouch(e.target, e);
}
});
这样可以减少事件监听器的数量,提升性能。但是要注意,touch 事件的冒泡机制和 mouse 事件略有不同,测试的时候要多注意。
还有一点,如果不需要特别精确的位置信息,可以用 CSS 的 pointer-events 来控制交互区域,减少 JS 的复杂度:
.no-interact {
pointer-events: none;
}
不过这种方式在 iOS 上有些兼容性问题,还是要小心使用。
兼容性和跨平台考虑
touchstart 在移动端基本都支持,但 PC 上的模拟触摸可能有问题。如果需要 PC 和移动端统一,要考虑鼠标事件的兼容:
function addUniversalEventListener(element, handler) {
element.addEventListener('touchstart', handler);
element.addEventListener('mousedown', handler);
}
// 分别处理touch和mouse事件
function onTouchStart(e) {
if (e.type === 'touchstart') {
const touch = e.touches[0];
// touch logic
} else if (e.type === 'mousedown') {
// mouse logic
}
}
这样可以保证在各种设备上都有基本的交互体验,虽然不能做到完全一致,但至少不会出大问题。
以上是我使用 touchstart 的一些实战经验,主要是针对滑动和点击结合的场景。这种方案经过多个项目验证,相对稳定可靠。如果你有更优的实现方式欢迎评论区交流。

暂无评论