15

我正在编写应该支持鼠标和触摸交互的 Web 应用程序。为了测试,我在 Windows 7 上使用触摸屏设备。我尝试在最新的 Firefox 和 Chrome canary 中嗅探触摸事件并得到以下结果:

触摸时 Firefox 触发触摸和相应的鼠标事件。Chrome 会触发touchstart/mousedowntouchend/mouseup成对,但mousemove以非常奇怪的方式触发:一/两次 while touchmove

所有鼠标事件都像往常一样处理。

有没有办法在现代触摸屏上同时处理鼠标和触摸事件?如果 Firefox 触发一对触摸和鼠标事件,Chrome 会发生touchmove什么mousemove?我应该将所有鼠标事件转换为触摸还是反之亦然?我希望找到正确的方法来创建响应式界面。

4

6 回答 6

18

您无法真正提前预测要监听哪些事件(例如,据您所知,在您的页面加载后可能会插入 USB 触摸屏)。

相反,您应该始终监听触摸事件和鼠标事件,但在您处理的触摸事件上调用 preventDefault() 以防止(现在是多余的)鼠标事件被触发。有关详细信息,请参阅http://www.html5rocks.com/en/mobile/touchandmouse/

于 2013-04-25T14:02:12.393 回答
8

您应该检查触摸界面的可用性并据此绑定事件。

你可以这样做:

(function () {
    if ('ontouchstart' in window) {
        window.Evt = {
            PUSH : 'touchstart',
            MOVE : 'touchmove',
            RELEASE : 'touchend'
        };
    } else {
        window.Evt = {
            PUSH : 'mousedown',
            MOVE : 'mousemove',
            RELEASE : 'mouseup'
        };
    }
}());

// and then...

document.getElementById('mydiv').addEventListener(Evt.PUSH, myStartDragHandler, false);


如果您想同时处理两者并且浏览器不能很好地将触摸事件转换为鼠标事件,您可以捕获触摸事件并停止它们 - 那么浏览器不应触发相应的鼠标事件(您不会有双重事件)你可以自己触发它作为鼠标事件或只​​是处理它。

var mydiv = document.getElementsById('mydiv');
mydiv.addEventListener('mousemove', myMoveHandler, false);
mydiv.addEventListener('touchmove', function (e) {
    // stop touch event
    e.stopPropagation();
    e.preventDefault();

    // translate to mouse event
    var clkEvt = document.createEvent('MouseEvent');
    clkEvt.initMouseEvent('mousemove', true, true, window, e.detail, 
                 e.touches[0].screenX, e.touches[0].screenY, 
                 e.touches[0].clientX, e.touches[0].clientY, 
                 false, false, false, false, 
                 0, null);
    mydiv.dispatchEvent(clkEvt);

    // or just handle touch event
    myMoveHandler(e);
}, false);
于 2013-01-25T22:51:20.053 回答
1

MouseEvents 和 TouchEvents 在技术上并不提供完全相同的功能,但对于大多数用途,它们可以互换使用。该解决方案并不偏爱一个,因为用户可能同时拥有鼠标和触摸屏。相反,它允许用户使用他们想要的任何输入设备,只要他们在更改输入之前等待至少五秒钟。当点击屏幕时,此解决方案会忽略触摸屏设备上的鼠标指针模拟。

var lastEvent = 3  ;
var MOUSE_EVENT = 1;
var TOUCH_EVENT = 2 ; 
 

element.addEventListener('touchstart', function(event)
		{
			 
			if (lastEvent === MOUSE_EVENT  )
			{
				var time =  Date.now() - eventTime  ;  
				if ( time > 5000 ) 
				{
					eventTime = Date.now() ;
					lastEvent = TOUCH_EVENT ;
					interactionStart(event) ;
				} 
			}
			else 
			{
				lastEvent = TOUCH_EVENT ; ;
				eventTime = Date.now() ;
				interactionStart(event)  ; 
			}
			 
		}) ;	

		element.addEventListener('mousedown',	function(event)
		{
			
			if (lastEvent === TOUCH_EVENT  )
			{
				var time =  Date.now() - eventTime  ;	
				if ( time > 5000 ) 
				{
					eventTime = Date.now() ;
					lastEvent = MOUSE_EVENT ;
					interactionStart(event) ;
				} 
			}
			else 
			{
				lastEvent=  MOUSE_EVENT ; 
				eventTime = Date.now() ;
				interactionStart(event)  ; 
			}
		}) ;  

function interactionStart(event) // handle interaction (touch or click ) here.
{...}

这绝不是一个双赢的解决方案,我已经使用了几次,并没有发现它的问题,但公平地说,我通常只是在它点击画布时使用它来启动动画,或者提供转动的逻辑一个 div 变成一个按钮。我把它留给大家使用这个代码,找到改进并帮助改进这个代码。(如果你没有找到更好的解决方案)。

于 2016-06-06T18:04:15.623 回答
1

我找到了这个线程,因为我有一个类似且更复杂的问题:

假设我们创建了一个带有箭头 NEXT/PREVIOUS 的启用 js 的可滚动区域,我们不仅要响应触摸和鼠标事件,还要在用户继续按下屏幕或按住他/她的鼠标时重复触发它们!

重复事件将使我的下一个按钮前进 2 个位置而不是一个!

在闭包的帮助下,一切似乎都是可能的:

(1)首先创建一个变量隔离的自调用函数:

 (function(myScroll, $, window, document, undefined){
 ...
 }(window.myScroll = window.myScroll || {}, jQuery, window, document));

(2) 然后,添加将保持内部状态的私有变量setTimeout()

/*
 * Primary events for handlers that respond to more than one event and devices  
 * that produce more than one, like touch devices.
 * The first event in browser's queue hinders all subsequent for the specific 
 * key intended to be used by a handler.
 * Every key points to an object '{primary: <event type>}'.
 */
var eventLock = {};
// Process ids based on keys.
var pids = {};
// Some defaults
var defaults = {
   pressDelay: 100 // ms between successive calls for continuous press by mouse or touch
}

(3) 事件锁功能:

function getEventLock(evt, key){
   if(typeof(eventLock[key]) == 'undefined'){
      eventLock[key] = {};
      eventLock[key].primary = evt.type;
      return true;
   }
   if(evt.type == eventLock[key].primary)
      return true;
   else
      return false;
}
function primaryEventLock(evt, key){
   eventLock[key].primary = evt.type;
}

(4) 附加您的事件处理程序:

function init(){
   $('sth').off('mousedown touchstart', previousStart).on('mousedown touchstart', previousStart);
   $('sth').off('mouseup touchend', previousEnd).on('mouseup touchend', previousEnd);
   // similar for 'next*' handlers
}

触发事件mousedown并将touchstart在支持两者的设备上产生对处理程序的双重调用(可能首先触发触摸)。这同样适用于mouseuptouchend

我们知道输入设备(实际上是整个图形环境)按顺序产生事件,所以我们不关心哪个先触发,只要将特殊键设置为私有eventLock.next.primary和分别从处理程序和eventLock.previous.primary捕获的第一个事件。next*()previous*()

是事件类型,因此第二个,第三个等事件始终是失败者,它们不会在锁定函数的帮助下获得锁定,eventLock()并且primaryEventLock()

(5) 上面可以看到事件处理器的定义:

function previousStart(evt){
   // 'race' condition/repetition between 'mousedown' and 'touchstart'
   if(!getEventLock(evt, 'previous'))
      return;

   // a. !!!you have to implement this!!!
   previous(evt.target);

   // b. emulate successive events of this type
   pids.previous = setTimeout(closure, defaults.pressDelay);

   // internal function repeats steps (a), (b)
   function closure(){
      previous(evt.target);
      primaryEventLock(evt, 'previous');
      pids.previous = setTimeout(closure, defaults.pressDelay);
   }
};
function previousEnd(evt){
      clearTimeout(pids.previous);
};

nextStart和类似nextEnd

这个想法是,无论谁在第一个(触摸或鼠标)之后来,都不会在 的帮助下获得锁定function eventLock(evt, key)并停在那里。

打开此锁的唯一方法是步骤*End()(4):previousEndnextEnd.

我还以一种非常聪明的方式处理了在会话中间连接的触摸设备的问题:我注意到连续按下的时间比为当时的主要事件defaults.pressDelay产生回调函数的连续调用(原因是没有end 事件处理程序终止 callabck)!

touchstart event
closure
closure
....
touchend event

我将用户使用的设备定义为主要设备,您只需按下更长的时间,您的设备就会在封闭装置的帮助下立即成为主要设备!primaryEventLock(evt, 'previous')

另请注意,执行所需的时间previous(event.target)应小于defaults.pressDelay.

(6) 最后,让我们暴露init()给全局作用域:

myScroll.init = init;

您应该用previous(event.target)手头的问题替换调用:fiddle

另外,请注意,在(5b)处,有一个解决另一个流行问题的方法,即我们如何将参数传递给调用 from 的函数setTimeout(),即setTimeout(previous, defaults.pressDelay)缺少参数传递机制。

于 2018-06-22T22:24:41.763 回答
1

这个线程上的解决方案已经过时了——对于那些(像我一样)在 2021 年仍然登陆这里的人来说,有一个新的 W3 指针事件规范。这些事件将鼠标和触摸合二为一。

https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events

https://www.w3.org/TR/pointerevents/

于 2021-10-22T16:45:57.490 回答
0

我一直在使用这个 jQuery 助手来绑定触摸和点击事件。

(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);
于 2013-05-19T21:25:49.500 回答