如何绑定'touchstart'和'click'事件但不对两者同时响应?

217
我正在开发一个移动网站,它需要在各种设备上运行。目前让我头疼的是黑莓设备。
我们需要支持键盘点击和触摸事件。
理想情况下,我只需使用:
$thing.click(function(){...})

但我们遇到的问题是,一些黑莓设备在触摸屏幕后触发点击的时间会有非常烦人的延迟。

解决方法是改用touchstart:

$thing.bind('touchstart', function(event){...})
但是我该如何绑定两个事件,但只触发其中一个?我仍然需要 click 事件来支持键盘设备,但当然,如果我使用的是触摸设备,则不希望 click 事件被触发。

附加问题:是否有办法同时适应甚至没有 touchstart 事件的浏览器?研究后发现,BlackBerry OS5 不支持 touchstart 事件,因此也需要依赖 click 事件。

补充:

也许更全面的问题是:

使用 jQuery,是否可能/建议使用相同的绑定处理触摸交互和鼠标交互?

理想情况下,答案是肯定的。如果不行,我有一些选择:

  1. 我们可以使用 WURFL 来获取设备信息,因此可以创建自己的设备矩阵。根据设备,我们将使用 touchstart 或 click。
  2. 通过 JS 检测浏览器是否支持触摸(我需要进行更多的研究,但似乎是可行的)。

但这仍然存在一个问题:对于同时支持触摸屏和键盘的设备怎么办呢?我们支持的一些手机(即诺基亚和黑莓)都具有触摸屏和键盘。因此,这让我又回到了最初的问题...是否有办法同时允许两者?


2
最好绑定touchstart和touchend事件,并在您的触摸逻辑旁编写自己的单击逻辑。内置的click回调函数不知道触摸操作。 - Justin808
@DA - 不,你根本不需要绑定到 .click() 回调函数。我会尝试用一些伪代码来写出答案。我手头没有触摸设备来编写真正的代码 :) - Justin808
啊,但是需要澄清的是,我仍然需要点击事件,因为会有使用非触摸设备访问此网站的人。 - DA.
2
使用.bind('touchstart mouseup')可以解决这个问题(基于下面的评论之一) - oriadam
请查看.one方法:http://api.jquery.com/one。 - oriadam
显示剩余4条评论
37个回答

3

2

另一种更易于维护的实现方式。然而,该技术也会执行event.stopPropagation()。点击事件在100ms内不会被任何其他元素捕获。

var clickObject = {
    flag: false,
    isAlreadyClicked: function () {
        var wasClicked = clickObject.flag;
        clickObject.flag = true;
        setTimeout(function () { clickObject.flag = false; }, 100);
        return wasClicked;
    }
};

$("#myButton").bind("click touchstart", function (event) {
   if (!clickObject.isAlreadyClicked()) {
      ...
   }
}

2

在我的情况下,这个方法完美地起作用:

jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
    jQuery(this).off('mouseup');
}
});

主要问题出现在我使用click替代mouseup时,对于触摸设备来说会同时触发click和touchend事件,如果我将click取消,一些在移动设备上的功能就无法使用了。click事件的问题在于它是一个全局事件,会触发其他事件,包括touchend事件。

2

仅供文档记录,这是我为最快/响应最佳的桌面点击/移动端轻触解决方案所做的:

我用一个修改过的函数代替了jQuery中的on函数,当浏览器支持触摸事件时,它会将所有的点击事件替换为touchstart事件。

$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
    on: (function(){
        var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
        return function( types, selector, data, fn, one ) {
            if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
            return this._on( types, selector, data, fn);
        };
    }()),
});

使用方法与之前完全相同,例如:

$('#my-button').on('click', function(){ /* ... */ });

但如果touchstart可用,则使用它,否则使用click。不需要任何延迟:D

1
这个问题的关键在于同时支持触摸和键盘的设备,你需要确保你能够适应两种交互方式。 - DA.
很好的观察 @DA。我添加了一个检查,只有在没有与之组合使用的任何触摸事件时才替换点击事件。这仍然不是每个项目的解决方案,但我相信它会适用于许多项目。 - Gerson Goulart

2

我刚想到了一个想法,可以记住是否触发了ontouchstart事件。在这种情况下,我们正在使用支持它的设备,并且希望忽略onclick事件。由于ontouchstart应该始终在onclick之前被触发,所以我在使用以下代码:

<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>


1
由于用户可能在同一页面访问期间使用触摸和鼠标,此代码片段的问题在于它仅注册touchAvailable标志一次而不是每个事件。来自https://dev59.com/92w05IYBdhLWcg3wy052#26145343的jQuery插件与您的代码相同,但可以告诉鼠标事件是通过触摸还是鼠标触发的,这不仅适用于点击事件,还适用于mouseleave事件,该事件可能会在鼠标进入之后几秒钟(或几分钟)触发(当使用触摸时,在鼠标手势或单击时扩展飞出菜单非常理想)。 - lmeurs

2
你可以尝试像这样做:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);

那些支持触摸和鼠标的设备怎么办? - Walle Cyril

1

这里没有提到,但你可能想查看这个链接:https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/

为了让后人回顾,不要尝试同时分配两个处理程序然后解决结果,而是可以简单地检查设备是否是触摸屏,只分配相关事件。如下所示:

var clickEvent = (function() {
  if ('ontouchstart' in document.documentElement === true)
    return 'touchstart';
  else
    return 'click';
})();

// and assign thusly:

el.addEventListener( clickEvent, function( e ){ 
    // things and stuff
});

我使用这个来绑定我的事件,以便在触摸屏上测试处理touchstartclick事件的设备,这些事件会触发两次,在我的开发PC上只有click被监听。但是,那个链接的作者提到了一个问题,即设计用于处理这两种事件的触摸屏笔记本电脑:

我了解到一种我没有考虑过的第三种设备,触摸屏笔记本电脑。它是一种支持触摸和点击事件的混合设备。绑定一个事件意味着只支持那个事件。这是否意味着拥有触摸屏和鼠标的用户必须显式触摸,因为那是我正在处理的唯一事件?

绑定touchstartclick似乎是处理这些混合设备的理想方式。为了防止事件触发两次,我在回调函数中添加了e.stopPropagation()e.preventDefault()e.stopPropagation()可以阻止事件“冒泡”到其父级,也可以防止第二个事件的触发。我包含e.preventDefault()是作为“以防万一”,但似乎可以省略。


1

对我来说,Mottie 给出的答案是最好的。我只是试图使他的代码更具可重用性,所以这是我的贡献:

bindBtn ("#loginbutton",loginAction);

function bindBtn(element,action){

var flag = false;
$(element).bind('touchstart click', function(e) {
    e.preventDefault();
    if (!flag) {
        flag = true;
        setTimeout(function() {
            flag = false;
        }, 100);
        // do something
        action();
    }
    return false;
});

1

这对我很有用,移动设备同时监听两个事件,因此要防止其中一个即触摸事件。桌面仅监听鼠标事件。

 $btnUp.bind('touchstart mousedown',function(e){
     e.preventDefault();

     if (e.type === 'touchstart') {
         return;
     }

     var val = _step( _options.arrowStep );
               _evt('Button', [val, true]);
  });

0

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