¿Cómo vincular eventos ‘touchstart’ y ‘click’ pero no responde a ambos?

Estoy trabajando en un sitio web móvil que tiene que funcionar en una variedad de dispositivos. El que me está dando un dolor de cabeza en este momento es BlackBerry.

Necesitamos admitir tanto clics de teclado como eventos táctiles.

Idealmente solo usaría:

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

pero el problema que nos encontramos es que algunos de estos dispositivos blackberry tienen un retraso muy molesto desde el momento en que se toca hasta que se activa un clic.

El remedio es usar touchstart en su lugar:

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

Pero, ¿cómo hago para vincular ambos eventos, pero solo disparo uno? Todavía necesito el evento de clic para dispositivos de teclado, pero por supuesto, no quiero que el evento de clic se active si estoy usando un dispositivo táctil.

Una pregunta adicional: ¿hay alguna forma de hacerlo y además acomodar navegadores que ni siquiera tienen un evento de inicio táctil? Al investigar esto, parece que BlackBerry OS5 no es compatible con el inicio táctil, por lo que también deberá confiar en los eventos de clic para ese navegador.

APÉNDICE:

Quizás una pregunta más completa es:

Con jQuery, ¿es posible / recomendable manejar tanto las interacciones táctiles como las interacciones del mouse con los mismos enlaces?

Idealmente, la respuesta es sí. Si no, tengo algunas opciones:

1) Usamos WURFL para obtener información del dispositivo para poder crear nuestra propia matriz de dispositivos. Dependiendo del dispositivo, usaremos touchstart O haga clic.

2) Detectar para soporte táctil en el navegador a través de JS (necesito investigar un poco más sobre eso, pero parece que es factible).

Sin embargo, eso todavía deja un problema: ¿qué pasa con los dispositivos que soportan AMBOS? Algunos de los teléfonos que admitimos (a saber, Nokias y BlackBerries) tienen pantallas táctiles y teclados. Entonces, ese tipo de información me lleva a un círculo completo de vuelta a la pregunta original … ¿hay alguna manera de permitir ambos de una vez?

Actualización : consulte el proyecto jQuery Pointer Events Polyfill , que le permite vincularse a eventos de “puntero” en lugar de elegir entre el mouse y el tacto.


Enlace a ambos, pero haga una bandera para que la función solo se dispare una vez cada 100 ms más o menos.

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

Esta es la solución que “creo” y elimina el GhostClick e implementa FastClick. Pruébalo y cuéntanos si te funcionó.

 $(document).on('touchstart click', '.myBtn', function(event){ if(event.handled === false) return event.stopPropagation(); event.preventDefault(); event.handled = true; // Do your magic here }); 

Podrías probar algo como esto:

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

Por lo general, esto también funciona:

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

Solo agregando return false; al final de la función de evento on("click touchstart") puede resolver este problema.

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

De la documentación de jQuery en .on ()

Si devuelve false de un controlador de eventos, se llamará automáticamente a event.stopPropagation() y event.preventDefault() . También se puede pasar un valor false para el manejador como una abreviación de function(){ return false; } function(){ return false; } .

Tenía que hacer algo similar. Aquí hay una versión simplificada de lo que funcionó para mí. Si se detecta un evento táctil, elimine el enlace de clic.

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

En mi caso, el evento click estaba vinculado a un elemento , así que tuve que eliminar el enlace de clic y volver a enlazar un evento de clic que impedía la acción predeterminada para el elemento .

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

Lo logré de la siguiente manera.

Pan comido…

 $(this).on('touchstart click', function(e){ e.preventDefault(); //do your stuff here }); 

En general, no desea mezclar la API predeterminada táctil y no táctil (clic). Una vez que te mueves al mundo táctil, es más fácil tratar solo con las funciones relacionadas con el tacto. Debajo hay un pseudo código que haría lo que usted quisiera.

Si se conecta en el evento touchmove y realiza un seguimiento de las ubicaciones, puede agregar más elementos en la función doTouchLogic para detectar gestos y otras cosas.

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

Creo que la mejor práctica ahora es usar:

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

touchend

El evento de touchend se dispara cuando se elimina un punto de contacto de la superficie táctil.

El evento touchend no activará ningún evento de mouse.


mouseup

El evento mouseup se envía a un elemento cuando el puntero del mouse está sobre el elemento y se suelta el botón del mouse. Cualquier elemento HTML puede recibir este evento.

El evento mouseup no activará ningún evento táctil.

EJEMPLO

 $('#click').on('mouseup', function () { alert('Event detected'); }); $('#touch').on('touchend', function () { alert('Event detected'); }); 
  

Click me

Touch me

compruebe los botones rápidos y los clics de chost desde google https://developers.google.com/mobile/articles/fast_buttons

Bueno … Todos estos son súper complicados.

Si tienes modernizr, es obvio.

 ev = Modernizr.touch ? 'touchstart' : 'click'; $('#menu').on(ev, '[href="#open-menu"]', function(){ //winning }); 

Otra implementación para un mejor mantenimiento. Sin embargo, esta técnica también hará event.stopPropagation (). El clic no queda atrapado en ningún otro elemento que haya hecho clic durante 100 ms.

 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()) { ... } } 

Solo para fines de documentación, esto es lo que hice para hacer clic en la solución móvil más rápido / más receptivo que pueda imaginar:

Reemplacé la función jQuery’s on con una versión modificada que, siempre que el navegador admita eventos táctiles, reemplazó todos mis eventos de clic con el inicio táctil.

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

Uso que sería exactamente el mismo que antes, como:

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

Pero usaría touchstart cuando esté disponible, haga clic cuando no lo esté. No se necesitan retrasos de ningún tipo: D

Me vino la idea de memorizar si ontouchstart alguna vez se activó. En este caso, estamos en un dispositivo que lo admite y desea ignorar el evento onclick . Como ontouchstart siempre debe ontouchstart antes de hacer onclick , estoy usando esto:

   

Podrías probar así:

 var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click'); $("#mylink").on(clickEvent, myClickHandler); 

Esto funcionó para mí, el móvil escucha a ambos, así que evita el que es el evento táctil. escritorio solo escucha el mouse.

  $btnUp.bind('touchstart mousedown',function(e){ e.preventDefault(); if (e.type === 'touchstart') { return; } var val = _step( _options.arrowStep ); _evt('Button', [val, true]); }); 

En mi caso, esto funcionó a la perfección:

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

El problema principal fue cuando, en lugar de mouseup, lo intenté con un clic, en los dispositivos táctiles se activaron los clics y touchend al mismo tiempo, si uso el clic apagado, alguna funcionalidad no funcionó en absoluto en los dispositivos móviles. El problema con el clic es que se trata de un evento global que activa el rest del evento, incluido touchend.

También estoy trabajando en una aplicación web para Android / iPad, y parece que si solo se usa “touchmove” es suficiente para “mover componentes” (no es necesario el inicio táctil). Al deshabilitar el inicio táctil, puede usar .click (); de jQuery. En realidad está funcionando porque no se ha sobrecargado con el inicio táctil.

Finalmente, puede binb .live (“touchstart”, función (e) {e.stopPropagation ();}); para solicitar que el evento touchstart deje de propagarse, sala de estar para hacer clic en () para activarlo.

Funcionó para mí

Hay muchas cosas que considerar al tratar de resolver este problema. La mayoría de las soluciones rompen el desplazamiento o no manejan correctamente los eventos de clic fantasma.

Para obtener una solución completa, consulte https://developers.google.com/mobile/articles/fast_buttons

NB: no puede manejar eventos de clic fantasma por elemento. La ubicación de la pantalla dispara un clic retrasado, por lo que si los eventos táctiles modifican la página de alguna manera, el evento click se enviará a la nueva versión de la página.

Puede ser efectivo asignar los eventos 'touchstart mousedown' o 'touchend mouseup' para evitar los efectos secundarios no deseados del uso del click .

Aprovechando el hecho de que un clic siempre seguirá a un evento táctil, esto es lo que hice para deshacerme del “clic fantasma” sin tener que usar los tiempos de espera ni los indicadores globales.

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

Básicamente cada vez que un evento ontouchstart se ontouchstart en el elemento, se ontouchstart un conjunto y luego se elimina (y se ignora), cuando llega el clic.

¿Por qué no utilizar la API de jQuery Event?

http://learn.jquery.com/events/event-extensions/

He utilizado este simple evento con éxito. Es limpio, con espacio de nombres y lo suficientemente flexible como para mejorar.

 var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); var eventType = isMobile ? "touchstart" : "click"; jQuery.event.special.touchclick = { bindType: eventType, delegateType: eventType }; 

Si está utilizando jQuery, lo siguiente funcionó bastante bien para mí:

 var callback; // Initialize this to the function which needs to be called $(target).on("click touchstart", selector, (function (func){ var timer = 0; return function(e){ if ($.now() - timer < 500) return false; timer = $.now(); func(e); } })(callback)); 

Otras soluciones también son buenas, pero estaba vinculando múltiples eventos en un bucle y necesitaba la función de auto llamada para crear un cierre apropiado. Además, no quería deshabilitar el enlace porque quería que fuera invocable en el siguiente clic / touchstart.

¡Podría ayudar a alguien en una situación similar!

Para funciones simples, solo tiene que reconocer tocar o hacer clic en Utilizar el siguiente código:

 var element = $("#element"); element.click(function(e) { if(e.target.ontouchstart !== undefined) { console.log( "touch" ); return; } console.log( "no touch" ); }); 

Esto devolverá “touch” si el evento touchstart está definido y “no touch” si no es así. Como dije, este es un enfoque simple para los eventos de clic / toque solo eso.

Estoy intentando esto y hasta ahora funciona (pero solo estoy en Android / Phonegap así que caveat emptor)

  function filterEvent( ob, ev ) { if (ev.type == "touchstart") { ob.off('click').on('click', function(e){ e.preventDefault(); }); } } $('#keypad').on('touchstart click', '.number, .dot', function(event) { filterEvent( $('#keypad'), event ); console.log( event.type ); // debugging only ... finish handling touch events... } 

No me gusta el hecho de que estoy volviendo a vincular a los manipuladores en cada toque, pero todos los aspectos considerados no ocurren muy a menudo (¡en tiempo de computación!)

Tengo una TONELADA de controladores como la de ‘#keypad’, por lo que tener una función simple que me permita lidiar con el problema sin demasiados códigos es la razón por la que fui por este camino.

Para mí, la mejor respuesta es la dada por Mottie, solo estoy tratando de hacer que su código sea más reutilizable, así que es mi contribución:

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

Intente utilizar Virtual Mouse (vmouse) Bindings de jQuery Mobile . Es un evento virtual especialmente para su caso:

 $thing.on('vclick', function(event){ ... }); 

http://api.jquerymobile.com/vclick/

Lista de soporte del navegador: http://jquerymobile.com/browser-support/1.4/

EDITAR: Mi respuesta anterior (basada en las respuestas de este hilo) no era el camino a seguir para mí. Quería que un submenú se expandiera al ingresar el mouse o presionar clic y colapsar al dejar el mouse u otro clic táctil. Dado que los eventos del mouse normalmente se disparan después de los eventos táctiles, fue un poco complicado escribir oyentes de eventos que admitan tanto la pantalla táctil como la entrada del mouse al mismo tiempo.

Plugin jQuery: Touch or Mouse

Terminé escribiendo un complemento jQuery llamado ” Touch or Mouse ” (897 bytes minified) que puede detectar si un evento fue invocado por una pantalla táctil o un mouse (¡sin probar el soporte táctil!). Esto permite el soporte de la pantalla táctil y el mouse al mismo tiempo y separa por completo sus eventos.

De esta forma, el OP puede usar touchstart o touchend para responder rápidamente a los clics táctiles y click para clics invocados solo con un mouse.

Demostración

Primero uno tiene que hacer, es decir. el elemento del cuerpo rastrea los eventos táctiles:

 $(document.body).touchOrMouse('init'); 

Los eventos del mouse están vinculados a los elementos de la manera predeterminada y al llamar a $body.touchOrMouse('get', e) podemos averiguar si el evento fue invocado por una pantalla táctil o un mouse.

 $('.link').click(function(e) { var touchOrMouse = $(document.body).touchOrMouse('get', e); if (touchOrMouse === 'touch') { // Handle touch click. } else if (touchOrMouse === 'mouse') { // Handle mouse click. } } 

Vea el plugin en el trabajo en http://jsfiddle.net/lmeurs/uo4069nh .

Explicación

  1. Este complemento necesita ser llamado, es decir. el elemento del cuerpo para rastrear los eventos touchstart y touchend , de esta forma el evento touchend no tiene que touchend en el elemento disparador (es decir, un enlace o botón). Entre estos dos eventos táctiles, este complemento considera que cualquier evento de mouse se invocará mediante touch.
  2. Los eventos del mouse se touchend solo después de touchend , cuando se touchend un evento del mouse dentro de ghostEventDelay (opción, 1000 ms de forma predeterminada) después de touchend , este complemento considera que el evento del mouse se invocará mediante el toque.
  3. Al hacer clic en un elemento usando una pantalla táctil, el elemento gana el :active estado :active . El evento mouseleave solo se mouseleave después de que el elemento pierde este estado por ejemplo. haciendo clic en otro elemento. Dado que esto podría ser segundos (¡o minutos!) Después de que se mouseenter evento de mouseenter , este complemento realiza un seguimiento del último evento de mouseenter de un elemento: si el último evento de mouseenter fue invocado por touch, el siguiente evento mouseleave también se considera invocado por toque.

Aquí hay una manera simple de hacerlo:

 // A very simple fast click implementation $thing.on('click touchstart', function(e) { if (!$(document).data('trigger')) $(document).data('trigger', e.type); if (e.type===$(document).data('trigger')) { // Do your stuff here } }); 

Básicamente guarda el primer tipo de evento que se dispara a la propiedad ‘trigger’ en el objeto de datos de jQuery que está adjuntado al documento raíz, y solo se ejecuta cuando el tipo de evento es igual al valor en ‘trigger’. En dispositivos táctiles, la cadena de eventos probablemente sea ‘touchstart’ seguida de ‘click’; sin embargo, el manejador ‘click’ no se ejecutará porque “click” no coincide con el tipo de evento inicial guardado en ‘trigger’ (“touchstart”).

La suposición, y creo que es segura, es que su teléfono inteligente no cambiará espontáneamente de un dispositivo táctil a un dispositivo de mouse o de lo contrario el grifo no se registrará porque el tipo de evento “desencadenante” solo se guarda una vez por la carga de la página y el “clic” nunca coincidirían con “touchstart”.

Aquí hay un codepen con el que puede jugar (intente tocar el botón en un dispositivo táctil, no debe haber ningún retraso en el clic): http://codepen.io/thdoan/pen/xVVrOZ

También implementé esto como un plugin de jQuery simple que también admite el filtrado de descendientes de jQuery al pasar una cadena de selector:

 // A very simple fast click plugin // Syntax: .fastClick([selector,] handler) $.fn.fastClick = function(arg1, arg2) { var selector, handler; switch (typeof arg1) { case 'function': selector = null; handler = arg1; break; case 'string': selector = arg1; if (typeof arg2==='function') handler = arg2; else return; break; default: return; } this.on('click touchstart', selector, function(e) { if (!$(document).data('trigger')) $(document).data('trigger', e.type); if (e.type===$(document).data('trigger')) handler.apply(this, arguments); }); }; 

Codepen: http://codepen.io/thdoan/pen/GZrBdo/

El mejor método que he encontrado es escribir el evento táctil y hacer que ese evento llame al evento click normal de forma programática. De esta manera tendrá todos sus eventos de clic normales y luego tendrá que agregar solo un controlador de eventos para todos los eventos táctiles. Para cada nodo que desee que sea tangible, simplemente agregue la clase “touchable” para invocar el controlador táctil. Con Jquery funciona así con un poco de lógica para asegurarse de que sea un evento táctil real y no un falso positivo.

 $("body").on("touchstart", ".touchable", function() { //make touchable items fire like a click event var d1 = new Date(); var n1 = d1.getTime(); setTimeout(function() { $(".touchable").on("touchend", function(event) { var d2 = new Date(); var n2 = d2.getTime(); if (n2 - n1 <= 300) { $(event.target).trigger("click"); //dont do the action here just call real click handler } }); }, 50)}).on("click", "#myelement", function() { //all the behavior i originally wanted });