我正在开发一个需要在多种设备上运行的移动网站。目前让我头疼的是黑莓手机。

我们需要同时支持键盘点击和触摸事件。

理想情况下,我会使用:

$thing.click(function(){...})

但我们遇到的问题是,一些黑莓设备从触摸到触发点击有一个非常恼人的延迟。

补救方法是使用touchstart:

$thing.bind('touchstart', function(event){...})

但是如何绑定两个事件,但只触发一个事件呢?对于键盘设备,我仍然需要click事件,但当然,如果我使用的是触摸设备,我不想让click事件触发。

一个额外的问题:有没有办法做到这一点,并额外适应那些甚至没有touchstart事件的浏览器?在研究中,看起来黑莓OS5不支持touchstart,因此也需要依赖于该浏览器的点击事件。

附录:

也许一个更全面的问题是:

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

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

我们使用WURFL来获取设备信息,这样就可以创建我们自己的设备矩阵。根据设备的不同,我们将使用touchstart或click。 通过JS检测浏览器中的触摸支持(我需要做更多的研究,但这似乎是可行的)。

然而,还有一个问题:支持这两种功能的设备怎么办?我们支持的一些手机(即诺基亚和黑莓)既有触摸屏又有键盘。这让我又回到了最初的问题……有没有一种方法可以同时兼顾两者?


当前回答

找到文档滚动移动的差异(水平和垂直)touchstart和touchend,如果其中一个大于1像素,那么它是移动而不是单击

var touchstartverscrollpos , touchstarthorscrollpos;


    $('body').on('touchstart','.thumbnail',function(e){

        touchstartverscrollpos = $(document).scrollTop();
        touchstarthorscrollpos = $(document).scrollLeft();


    });



    $('body').on('touchend','.thumbnail',function(e){


        var touchendverscrollpos = $(document).scrollTop();
        var touchendhorscrollpos = $(document).scrollLeft();

        var verdiff = touchendverscrollpos - touchstartverscrollpos;
        var hordiff = touchendhorscrollpos - touchstarthorscrollpos;


        if (Math.abs(verdiff) <1 && Math.abs(hordiff)<1){

// do you own function () here 



            e.stopImmediatePropagation();

            return false;
        }

    });

其他回答

在试图解决这个问题时,有很多事情需要考虑。大多数解决方案要么中断滚动,要么不能正确处理幽灵点击事件。

完整的解决方案请参见https://developers.google.com/mobile/articles/fast_buttons

注意:你不能在每个元素的基础上处理鬼点击事件。延迟的点击是由屏幕位置触发的,所以如果你的触摸事件以某种方式修改了页面,点击事件将被发送到页面的新版本。

利用点击总是会在触摸事件之后发生的事实,以下是我在不使用超时或全局标志的情况下消除“幽灵点击”的方法。

$('#buttonId').on('touchstart click', function(event){
    if ($(this).data("already")) {
        $(this).data("already", false);
        return false;
    } else if (event.type == "touchstart") {
        $(this).data("already", true);
    }
    //your code here
});

基本上,只要在元素上触发ontouchstart事件,就会设置一个标记,然后在点击到来时删除(并忽略)。

对于我来说,最好的答案是motie给出的,我只是试图让他的代码更可重用,所以这是我的贡献:

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;
});

这里没有提到,但你可能想看看这个链接: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
});

我使用它来绑定我的事件,这样我就可以在触摸屏上测试,处理touchstart和click事件,会触发两次,在我的开发PC上只听到点击

不过,该链接的作者提到了一个问题,即设计用于处理这两种事件的触摸屏笔记本电脑:

I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling? Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.

我不得不做一些类似的事情。下面是一个对我有用的简化版本。如果检测到触摸事件,则删除单击绑定。

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click');

  //your code here
});

在我的例子中,点击事件被绑定到<a>元素,所以我必须删除点击绑定并重新绑定点击事件,这阻止了<a>元素的默认操作。

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click').on('click', function(e){ e.preventDefault(); });

  //your code here
});