touchstart事件在移动端开发中的那些坑和解决方案

Newb.松浩 移动 阅读 2,533
赞 23 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

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 的一些实战经验,主要是针对滑动和点击结合的场景。这种方案经过多个项目验证,相对稳定可靠。如果你有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论