如何绑定'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个回答

145

更新:请查看jQuery Pointer Events Polyfill 项目,它允许您绑定“指针”事件而不是选择鼠标和触摸之间。


同时绑定两者,但是设置一个标志,使函数每100毫秒只触发一次。

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

  return false
});

7
嗯...感觉有点粗糙,但这可能可行。问题在于在某些设备上,这会出现明显的延迟...也许几乎达到一秒钟...这会对使用速度更快设备的其他人造成烦恼。 - DA.
15
click改为mousedown,也许长时间的延迟是mousedownmouseup之间的区别,这就是click被确定的方式。 - Mottie
7
这是我采用的解决方案。我的做法是在 touchend 时标记一个项目,应用一个名为 touched 的类。这会在调用点击事件之前被激发。然后我添加了一个点击事件,首先检查该类是否存在。如果存在,则我们假设触摸事件已经被触发,我们不会对点击事件进行任何操作,只需删除类名以 "重置" 它以进行下一次交互。如果该类不存在,则我们假设使用键盘单击了该项,并从那里触发函数。 - DA.
7
请注意,大多数手机浏览器在单击事件上有一个300毫秒的延迟,因此您应该将延迟增加至至少300毫秒。请参阅关于300毫秒延迟问题的这篇文章。这基本上是为了启用“双击缩放”功能,在早期iPhone时代这是非常重要的功能,当时大多数网站都是设计用于在大屏幕桌面上查看。 - awe
16
这个问题目前已经被浏览了接近10万次。这似乎是一个极其普遍的用例/问题。然而,这个答案和其他类似问题上的答案都似乎很操纵巧妙。虽然谷歌的解决方案比大多数更全面考虑,但只是看起来更加健壮的方法。对于像点击处理程序这样简单的事情,解决方案似乎应该很简单。难道没有人为这个问题找到非操纵巧妙的解决方案吗? - jinglesthula
显示剩余14条评论

76

这是我“创建”的修复,它解决了GhostClick,实现了FastClick。请自行尝试并告诉我们是否有效。

$(document).on('touchstart click', '.myBtn', function(event){
        if(event.handled === false) return
        event.stopPropagation();
        event.preventDefault();
        event.handled = true;

        // Do your magic here

});

2
helgatheviking:http://jsbin.com/ijizat/25 在JavaScript中有一个名为TouchClick的函数,它包含了上述内容。 - Matt Parkins
5
我强烈倾向于这个答案提供的方法,因为它不会测试设备能力,也不会使用setTimeout。我有过类似问题的解决方案,使用setTimeout可能对大多数情况有效,但事件的时间安排相当随意且与设备相关。 - itsmequinn
8
这种方法行不通,因为在匿名函数中传递'event'参数会将其变成该函数中的本地对象,所以每次触发事件时event.handled都将等于undefined。以下是一个解决方案:使用jQuery.data()在目标元素本身中设置'handled'标志。 - thdoan
4
据我理解(并测试过),event.stopPropagation();event.preventDefault();只能阻止事件触发一次。因此,为什么还要检查if(event.handled !== true)呢?对我来说这并不必要,除非我遗漏了什么。 - nbar
2
event.handled 应该被检查是否为 true 吗?据我所知它从来不是 false。它最初是 undefined,然后您想知道它是否已设置为 true - ACJ
显示剩余11条评论

49

你可以尝试像这样操作:

var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);

5
我认为这个问题在于它测试的是设备的能力,而不是用户所做的事情,对吧?挑战在于我们需要支持同时拥有触摸屏和键盘的设备(因此他们可能会同时使用两者)。 - DA.
8
这个想法不好,因为当使用鼠标时,触摸屏和鼠标的用户会遇到事件中断的问题(现在带有触摸屏的笔记本电脑越来越普及)。 - Kristian J.
1
这是一篇与此相关的精彩文章:https://hacks.mozilla.org/2013/04/detecting-touch-its-the-why-not-the-how/ - Luke Melia
Windows 8将始终连接'touchstart'事件,如果使用这个事件,因为在这个操作系统中'ontouchstart'将返回'true'。因此,除非您的代码仅在真正的触摸设备上运行,否则这可能不是最好的答案,但如果是这种情况,您根本不需要进行检查。 - Neil Monroe
在带有触摸屏的笔记本电脑上,用户将无法使用此代码在Chrome中单击任何内容(至少在Chrome 46中)。不过,IE 11似乎可以正常工作。 - David Sherret
最终我采用了这种方法的一个版本,而不是寻找document.ontouchstart,我在用户第一次触摸屏幕时设置了clickEventType。这并不能解决那些在同一设备上不断地在鼠标和触摸屏之间切换的用户的问题,但至少它不会因为事件可能发生而在用户甚至没有触摸屏幕的情况下切换到“触摸模式”。 - skybondsor

43

通常这也是有效的:

$('#buttonId').on('touchstart click', function(e){
    e.stopPropagation(); e.preventDefault();
    //your code here

});

8
如果你正在使用较新的jQuery版本,那么@Jonathan是错误的。"Live"已经在1.7版本中被弃用了。 - Mike Barwick
我也尝试过(并且我在其他地方读到过-不是我的想法)使用e.stopPropagation(); e.preventDefault();,它几乎可以正常工作(对我来说),但后来我发现实际上它可以正常工作20次,但有1次不行,所以它不是万无一失的。请看这里:https://dev59.com/NoPba4cB1Zd3GeqPnxsc 期待您对此问题的评论! - Garavani
@MikeBarwick,您能否解释一下您的评论或提供一个解释它的链接?非常感谢。 - Billal Begueradj

28

只需在on("click touchstart")事件函数的末尾添加return false;即可解决此问题。

$(this).on("click touchstart", function() {
  // Do things
  return false;
});

来自jQuery关于.on()的文档

从事件处理程序中返回false会自动调用event.stopPropagation()event.preventDefault()。也可以将一个false值作为处理程序的简写传递,例如function(){ return false; }


3
在同时拥有两个设备的情况下,它只触发一次,效果很好 - 对我来说,这似乎是最干净、最不繁琐的解决方案。 - Adam Cooper
对我来说效果最好,而且实际上也很有道理。 - Amir5000
1
有什么缺点吗?为什么这样一个简单的解决方案不被广泛知晓? - ESR

11

我也曾经遇到类似的问题。以下是我成功解决的一个简化版本。如果检测到触摸事件,则删除单击绑定。

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

我希望这个可以直接工作,但事实并非如此。$(this)指向的不是你想象中的那个。 - Dave Lowerre

8
我是这样做成功的。
非常简单...
$(this).on('touchstart click', function(e){
  e.preventDefault();
  //do your stuff here
});

5
这个问题的意思是:如果一个设备同时注册了点击和触摸事件,那么这段代码不会重复触发两次吗? - DA.
8
抱歉,我明白你的意思了。是的,轻触会产生touchstart事件和click事件。这被称为幽灵点击。谷歌已经针对此问题实施了解决方案。虽然可能不直接,但却非常有效。这是链接:http://code.google.com/mobile/articles/fast_buttons.html#ghost - Hasanavi
2
可能很简单,但是你在function()中缺少了e参数。 - ProblemsOfSumit
@Hasanavi 这是一个很好的修复,但我发现使用 .on 会有更好的结果。 - Neil
1
谢谢@Neil。是的,使用.on是一个更好的想法,因为它可能会在未来版本中被删除。我已经将我的示例从bind更改为on。 - Hasanavi

6

我认为现在最好的做法是使用:

$('#object').on('touchend mouseup', function () { });

touchend

touchend事件在触点从触摸表面移除时触发。

touchend事件不会触发任何鼠标事件。


mouseup

mouseup事件在鼠标指针悬停在元素上并释放鼠标按钮时发送到元素。任何HTML元素都可以接收此事件。

mouseup事件不会触发任何触摸事件。

示例

$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>


编辑(2017)

截至2017年,从Chrome开始的浏览器正在采取措施使点击事件.on("click")在鼠标和触摸屏上更加兼容,通过消除点击请求上的触摸事件引起的延迟。

这意味着回归使用只有点击事件将是未来最简单的解决方案。

我还没有进行任何跨浏览器测试,以查看这是否可行。


这对我来说似乎很有前途,所以我试着玩了一下,但最终我发现 mouseup 的结果不可预测,特别是在使用触摸板而不是传统鼠标时。 - skybondsor
使用mouseup事件可能会导致问题,特别是在使用滚动内容时,您希望用户拖动而不是单击。我认为最好的方法是使用多个侦听器,并设置适当的返回值为false以限制进一步的冒泡。 - Lenn Dolling

3

通常情况下,不建议混合使用默认的触摸和非触摸(点击)API。一旦进入触摸领域,最好只涉及与触摸相关的功能。以下是一些伪代码,可以实现您想要的功能。

如果在touchmove事件中连接并跟踪位置,您可以在doTouchLogic函数中添加更多项目以检测手势等内容。

var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;

$thing.bind('touchstart'), function() {
     var d = new Date();
     touchStartTime = d.getTime();
     touchStartLocation = mouse.location(x,y);
});

$thing.bind('touchend'), function() {
     var d = new Date();
     touchEndTime= d.getTime();
     touchEndLocation= mouse.location(x,y);
     doTouchLogic();
});

function doTouchLogic() {
     var distance = touchEndLocation - touchStartLocation;
     var duration = touchEndTime - touchStartTime;

     if (duration <= 100ms && distance <= 10px) {
          // Person tapped their finger (do click/tap stuff here)
     }
     if (duration > 100ms && distance <= 10px) {
          // Person pressed their finger (not a quick tap)
     }
     if (duration <= 100ms && distance > 10px) {
          // Person flicked their finger
     }
     if (duration > 100ms && distance > 10px) {
          // Person dragged their finger
     }
}

我想这就是问题的关键:是否有意义尝试使用一个代码库支持两种模型。我会在我的问题中更新一些场景。 - DA.
1
通常情况下,您不希望混合使用默认的触摸和非触摸。经过这样一番思考,我同意这种说法。问题在于,他们不断推出带有键盘的触摸设备,这对我们开发人员来说是一个头疼的问题。 - DA.
我同意,在开始时做出最终的JS决策触摸或不触摸会更容易得多。这将是一个多么简单的世界啊!但是那些该死的点击和触摸敏感设备怎么办?因为它们值得所有这些头痛吗?我在想,这就是未来吗? - Garavani
我不喜欢函数有多个定义的想法。我的公司目前正在开发一个项目,其中iPad并没有被特别视为移动设备,仍然需要使用touchend事件。但是在普通桌面版本上,touchend事件无效。所以@mottie提出的解决方案对于这种情况绝对是合适的。 - Pete

3

嗯...这些都非常复杂。

如果您有现代浏览器检测库,那么这就易如反掌了。

ev = Modernizr.touch ? 'touchstart' : 'click';

$('#menu').on(ev, '[href="#open-menu"]', function(){
  //winning
});

2
你能解释一下它是做什么的吗?我相信它会检查设备是否支持触摸,如果不支持,则使用点击。问题在于(或者至少在我写这个问题时是这样),有些设备同时支持两种方式。在这些设备上,我仍然需要处理这两种事件,因为触发器可能来自触摸或点击。 - DA.
2
我建议多解释一点。 - Russia Must Remove Putin

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