JavaScript touchend与click之间的困境

55

我正在设计一些 Javascript UI,并且使用了很多触摸事件,例如“touchend”以提高触摸设备的响应速度。然而,有一些逻辑问题一直在困扰着我...

我发现很多开发人员在同一个事件中混合使用“touchend”和“click”。在许多情况下,这不会有影响,但基本上该函数将在触摸设备上触发两次:

button.on('click touchend', function(event) {
  // this fires twice on touch devices
});

有人建议可以检测触摸功能,并相应地设置事件,例如:

var myEvent = ('ontouchstart' in document.documentElement) ? 'touchend' : 'click';
button.on(myEvent, function(event) {
  // this fires only once regardless of device
});

以上的问题在于,它将在同时支持触摸和鼠标的设备上出现问题。如果用户正在双输入设备上使用鼠标,则“点击”不会触发,因为仅为按钮分配了“touchend”。

另一种解决方案是检测设备(例如“iOS”)并基于此分配事件: iPad 触摸结束时单击事件被调用两次。 当然,上面链接中的解决方案仅适用于 iOS(而非 Android 或其他设备),似乎更像是解决相当基本的东西的“hack”。

另一种解决方案是检测鼠标移动,并结合触摸功能来确定用户是在使用鼠标还是触摸。问题在于用户可能没有在您要检测它的时间内移动鼠标...

我能想到最可靠的解决方案是使用一个简单的防抖函数,以确保函数在短时间间隔内(例如100毫秒)仅触发一次:

button.on('click touchend', $.debounce(100, function(event) {
  // this fires only once on all devices
}));

我是否遗漏了什么,或者是否有更好的建议?

编辑:在我的帖子后,我发现了这个链接,它提出了与上面类似的解决方案:如何绑定'touchstart'和'click'事件但不对两者都做出响应?


2
那并没有解决问题... Modernizr 只是检测“触摸”。在触摸设备上,事件仍然会触发两次。或者,如果你通过 modernizr 为触摸设备分配了“触摸”事件,它将停止对使用鼠标的双输入设备(如许多 Windows8 设备)的用户起作用。 - suncat100
现在,您是否可以使用pointerup事件,以便独立于使用的设备只发射一次。 - Philippe
6个回答

34
经过一天的研究,我发现最好的解决方案是坚持使用click并使用https://github.com/ftlabs/fastclick来消除触摸延迟。我不100%确定这与touchend一样有效,但至少相差不远。
我确实找到了一种通过使用stopPropagationpreventDefault禁用双重触摸触发事件的方法,但这很棘手,因为它可能会干扰应用在元素上的其他触摸手势:
button.on('touchend click', function(event) {
  event.stopPropagation();
  event.preventDefault();
  // this fires once on all devices
});

实际上我正在寻找一种解决方案来结合一些UI元素上的touchstart,但我不知道如何将其与click结合,除了上面的解决方案。


有没有本地方法可以检测浏览器是否支持touchstart/end? - Ben Racicot
1
你是如何在没有父包装器的情况下,在同一个按钮元素上使用e.stopPropagation()让它正常工作的?我曾尝试通过在angular中附加(click)和(touchend),并在它们都依附的方法上使用e.stopPropagation(),但它仍然会触发两次。 - Jonathan002

10
这个问题已经得到解答,但可能需要更新。
根据谷歌的通知,如果我们在<head>元素中包含以下行,则不再有300-350毫秒的延迟。
<meta name="viewport" content="width=device-width">

就是这样!以后点击和触摸事件之间将不再有任何区别!


1
有趣。也许是时候摆脱 fastclick.js 了... 我会在移动浏览器中进行一些测试。 - suncat100
1
元标签应该放在head标签中,而不是header标签中。 - James
3
这对我来说并不正确。 - skybondsor
没有300毫秒延迟会如何解决不需要同时需要点击和touchend事件的问题?我听说一些安卓设备不会触发点击事件。 - Jonathan002

0

是的,禁用双击缩放(因此也禁用了点击延迟)通常是最好的选择。我们终于有了一个好的建议来做到这一点,这将很快在所有浏览器上起作用。

如果出于某种原因,您不想这样做。您还可以使用UIEvent.sourceCapabilities.firesTouchEvents来明确忽略冗余的click。这个polyfill与您的去抖动代码类似。


polyfill链接已经消失了。 - OG Sean

0

对我来说,在HTML元素本身中使用“onclick”功能,既适用于触摸操作也适用于点击操作。

<div onclick="cardClicked(this);">Click or Touch Me</div>

0

你可以按照以下方式实现。

function eventHandler(event, selector) {
    event.stopPropagation(); // Stop event bubbling.
    event.preventDefault(); // Prevent default behaviour
    if (event.type === 'touchend') selector.off('click'); // If event type was touch turn off clicks to prevent phantom clicks.
}

// Implement
$('.class').on('touchend click', function(event) {
    eventHandler(event, $(this)); // Handle the event.
    // Do somethings...
});

0

你的 debounce 函数将延迟处理每个点击事件 100 毫秒:

button.on('click touchend', $.debounce(100, function(event) {
  // this is delayed a minimum of 100 ms
}));

相反,我创建了一个cancelDuplicates函数,它会立即触发,但是在10毫秒内的任何后续调用都将被取消:

function cancelDuplicates(fn, threshhold, scope) {
    if (typeof threshhold !== 'number') threshhold = 10;
    var last = 0;

    return function () {
        var now = +new Date;

        if (now >= last + threshhold) {
            last = now;
            fn.apply(scope || this, arguments);
        }
    };
}

使用方法:

button.on('click touchend', cancelDuplicates(function(event) {
  // This fires right away, and calls within 10 ms after are cancelled.
}));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接