如何绑定Mousedown和Touchstart事件,但不对两者都响应? Android、JQuery

57

在开发一个可以在移动设备上浏览的网站时,需要同时绑定在touchstart和mousedown事件上执行的操作。

看起来像这样:

 $("#roll").bind("mousedown touchstart", function(event){

 someAction();

在 iPhone 上表现良好,但在 Android 上会响应两次。

event.stopPropagation();
event.preventDefault();

将这段代码添加后解决了Android Chrome的问题,但未能解决Android默认浏览器的问题。还有其他技巧可以解决所有Android的问题吗?


event.preventDefault() 应该可以停止 mousedown 事件。 这个问题比较老旧,所以希望现在已经解决了。我之前也遇到过同样的问题,最后发现是 jquery touchpunch 引起的。 - Maciej Krawczyk
1
这个相同的问题可能会比这里的答案更有帮助:https://dev59.com/92w05IYBdhLWcg3wy052#25133023 - Andrew
10个回答

38
element.on('touchstart mousedown', function(e) {
    e.preventDefault();
    someAction();
});

preventDefault取消事件,根据规范

你会得到touchstart,但是一旦你取消了它,你将不再得到mousedown。与被接受的答案所说的相反,除非有必要,否则你不需要调用stopPropagation。即使被取消,事件仍然会正常传播。浏览器会忽略它,但是你的钩子仍然会起作用。

Mozilla在这一点上同意

调用preventDefault()在touchstart或者一个系列中的第一个touchmove事件可以防止相应的鼠标事件触发。

编辑:我刚刚重新读了问题,你说你已经这样做了,而且它并没有修复Android默认浏览器的问题。不确定被接受的答案如何帮助,因为它基本上做了相同的事情,只是以更复杂的方式和具有事件传播错误(touchstart不传播,但click传播)。


1
在 Android 上,我收到了“忽略了尝试使用 cancelable=false 取消 touchmove 事件的错误,例如因为滚动正在进行中且无法中断”的提示。 - Curtis
1
你可能需要添加 addEventListener('touchstart', FUNCTION, {passive: false});。 - Walle Cyril
2
那样做可以实现功能,但是你甚至无法通过滑动来滚动页面。 - Curtis
如果你想要同时响应触摸和鼠标事件,有可能你实际上需要的是PointerEvents,它可以处理触摸和鼠标事件。 - Michael

32

我一直在使用这个函数:

//touch click helper
(function ($) {
    $.fn.tclick = function (onclick) {

        this.bind("touchstart", function (e) { 
            onclick.call(this, e); 
            e.stopPropagation(); 
            e.preventDefault(); 
        });

        this.bind("click", function (e) { 
           onclick.call(this, e);  //substitute mousedown event for exact same result as touchstart         
        });   

        return this;
    };
})(jQuery);

更新:修改答案以支持鼠标和触摸事件共同使用。


9
请注意,在 Windows 8 上,当 Chrome 和 Firefox 检测到触摸屏时,将启用触摸事件。因此,用户可以在鼠标和触摸输入之间切换。通过使用这段代码,鼠标事件将被忽略。 - gregers
1
@gregers,你说得对也不对。Windows 8支持触摸和鼠标事件,但是当触摸时,你可以取消“虚拟”鼠标事件,只处理触摸,这不会影响真实的鼠标事件,因为它们不会触发触摸事件。 - daniel.gindi
@daniel.gindi 我的评论是针对此答案之前的版本的:http://stackoverflow.com/posts/14202543/revisions - gregers
13
touchstart 相当于 mousedown 而不是 clickclick 需要用户按下按钮。 我不认为这两者会产生完全相同的结果。 - George Reith
1
你如何解除绑定此事件?比如说,如果你想临时添加监听器,然后停止事件触发? - hofnarwillie
显示剩余2条评论

3
这是一个非常古老的问题,但我遇到了同样的问题,并找到了另一种解决方法,不需要使用 stopPropagation()preventDefault() 或嗅探设备类型。我假设设备支持触摸和鼠标输入,来开发这个解决方案。
解释:当触摸被初始化时,事件的顺序是 1)touchstart 2)touchmove 3)touchend 4)mousemove 5)mousedown 6)mouseup 7)click。基于此,我们将从 touchstart(链中的第一个)到 click(链中的最后一个)标记触摸交互。如果在此触摸交互之外注册了 mousedown,则可以安全地进行拾取。
以下是 Dart 中的逻辑,应该很容易在 js 中复制。
var touchStarted = false;
document.onMouseDown.listen((evt) {
  if (!touchStarted) processInput(evt);
});
document.onClick.listen((evt) {
  touchStarted = false;
});
document.onTouchStart.listen((evt) {
  touchStarted = true;
  processInput(evt);
});

正如您所看到的,我的监听器被放置在document上。因此,我不能使用stopPropagation()preventDefault()来阻止这些事件,以便它们可以冒泡到其他元素。这个解决方案帮助我单独处理一个交互操作,并希望它能对您有所帮助!


3
考虑到Gregers对Win8和Chrome/Firefox的评论,Skyisred的评论看起来并不那么蠢(:P @所有的批评者)尽管我更喜欢使用黑名单而不是他建议的白名单,只排除Android触摸绑定:
var ua = navigator.userAgent.toLowerCase(),
isAndroid = ua.indexOf("android") != -1,
supportsPointer = !!window.navigator.msPointerEnabled,
ev_pointer = function(e) { ... }, // function to handle IE10's pointer events
ev_touch = function(e) { ... }, // function to handle touch events
ev_mouse = function(e) { ... }; // function to handle mouse events

if (supportsPointer) { // IE10 / Pointer Events
    // reset binds
    $("yourSelectorHere").on('MSPointerDown MSPointerMove MSPointerUp', ev_pointer);
} else {
    $("yourSelectorHere").on('touchstart touchmove touchend', ev_touch); // touch events
    if(!isAndroid) { 
        // in androids native browser mouse events are sometimes triggered directly w/o a preceding touchevent (most likely a bug)
        // bug confirmed in android 4.0.3 and 4.1.2
        $("yourSelectorHere").on('mousedown mousemove mouseup mouseleave', ev_mouse); // mouse events
    }
}

顺便说一下:我发现当使用stopPropagation和preventDefault时,鼠标事件并不总是被触发。具体来说,在touchend事件之前,我只注意到额外的mousemove事件...这是一个非常奇怪的错误,但上面的代码对我在测试过的所有平台上都起作用了(包括Mac OS、Windows、iOS 5+6、Android 2+4以及各自的原生浏览器、Chrome、Firefox、IE、Safari和Opera,如果有的话)。


这太啰嗦了,你能想象每次IE发布新版本时都要维护所有的东西吗? - webkitfanz

3

哇,这个问题和相关问题中有很多答案,但是没有一个对我有效(针对Chrome,移动响应,mousedown + touchstart)。但是这个:

Original Answer

翻译成:

最初的回答

(e) => {
  if(typeof(window.ontouchstart) != 'undefined' && e.type == 'mousedown') return;

  // do anything...
}

务实的解决方案 :) - halfbit

1

使用此代码已修复

var mobile   = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); 
var start = mobile ? "touchstart" : "mousedown";
$("#roll").bind(start, function(event){

28
使用特性检测(如Joshua的回答)而不是UA检测。 - Thomas
3
这是最佳答案,也是唯一可行的答案。Joshua的答案没有使用特征检测。你需要检测的唯一特征就是安卓当前的怪异行为。如果你检测到有触摸事件的特征,那么它将禁用在触摸屏笔记本电脑上的鼠标。e.preventDefault()在安卓上无效。 - Curtis
2
触摸和鼠标兼容的设备怎么办? - Walle Cyril
这也无法正常工作。我已尝试使用类范围和非范围进行测试,每种方式都有自己的问题。在使用类范围的情况下,无论你如何使用touchstartmousedown触发函数,它只会触发mousedown。没有范围时,它会不断触发你第一次触发的那个结果。 - l3lue

0

编写此代码并添加j query punch touch js。它将使用触摸事件模拟鼠标事件。

function touchHandler(event)
{
    var touches = event.changedTouches,
        first = touches[0],
        type = "";
         switch(event.type)
    {
        case "touchstart": type = "mousedown"; break;
        case "touchmove":  type="mousemove"; break;        
        case "touchend":   type="mouseup"; break;
        default: return;
    }

    var simulatedEvent = document.createEvent("MouseEvent");
    simulatedEvent.initMouseEvent(type, true, true, window, 1, 
                              first.screenX, first.screenY, 
                              first.clientX, first.clientY, false, 
                              false, false, false, 0/*left*/, null);
    first.target.dispatchEvent(simulatedEvent);
    event.preventDefault();
}

function init() 
{
    document.addEventListener("touchstart", touchHandler, true);
    document.addEventListener("touchmove", touchHandler, true);
    document.addEventListener("touchend", touchHandler, true);
    document.addEventListener("touchcancel", touchHandler, true);    
} 

0

我建议你尝试使用 jquery-fast-click。我在这个问题上试过其他方法,还有其他方法。每种方法都解决了一个问题,但又引入了另一个问题。fast-click在Android、iOS、桌面浏览器和桌面触摸浏览器上第一次就起作用了(呻吟声)。


0

这个本地解决方案对我来说效果最好:

  1. 在文档中添加一个 touchstart 事件,设置全局变量 touch = true
  2. 在 mousedown/touchstart 处理程序中,当检测到触摸屏时,阻止所有 mousedown 事件:if (touch && e.type === 'mousedown') return;

-1

我认为最好的方法是:

var hasTouchStartEvent = 'ontouchstart' in document.createElement( 'div' );

document.addEventListener( hasTouchStartEvent ? 'touchstart' : 'mousedown', function( e ) {
    console.log( e.touches ? 'TouchEvent' : 'MouseEvent' );
}, false );

2
然后我的鼠标在触摸屏笔记本上就无法使用了。如果您为mousedown添加另一个事件,Android将同时触发两个事件。 - Curtis
大多数新款笔记本电脑也支持触摸屏,因此会有这些事件发生。请不要这样做! - EoghanM

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