¿Cómo vincular eventos 'touchstart' y 'click' pero no responder a ambos?


198

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

Necesitamos admitir tanto los clics del teclado como los eventos táctiles.

Idealmente, solo usaría:

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

pero el problema con el 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 para disparar uno? Todavía necesito el evento de clic para dispositivos de teclado, pero, por supuesto, no quiero que se active el evento de clic si estoy usando un dispositivo táctil.

Una pregunta adicional: ¿hay alguna forma de hacer esto 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 tendrá que depender de los eventos de clic para ese navegador.

APÉNDICE:

Quizás una pregunta más completa es:

Con jQuery, ¿es posible / recomendado manejar las interacciones táctiles y 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 haremos clic.

2) Detectar 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 admiten AMBOS? Algunos de los teléfonos que admitimos (a saber, Nokias y BlackBerries) tienen pantallas táctiles y teclados. Entonces, eso me lleva de vuelta a la pregunta original ... ¿hay alguna manera de permitir ambas cosas a la vez de alguna manera?


2
Es mejor vincularse al inicio táctil y al toque y escribir su propia lógica de clic junto con su lógica táctil. La devolución de llamada de clic incorporada ya que no conoce los toques.
Justin808

No estoy seguro de seguirlo, Justin. ¿No seguiría teniendo un evento de inicio táctil y clic vinculado?
DA.

@DA: no, no se uniría a la devolución de llamada .click () en absoluto. Intentaré escribir una respuesta en algún código sudo. No tengo un dispositivo táctil a mano para escribir código real :)
Justin808

Ah, pero para aclarar, todavía necesito eventos de clic, ya que habrá personas que accedan a este sitio con dispositivos no táctiles.
DA.

2
El uso .bind('touchstart mouseup')lo resolverá (según uno de los comentarios a continuación)
oriadam

Respuestas:


135

Actualización : Echa un vistazo al proyecto PolyQill de jQuery Pointer Events que te permite enlazar eventos de "puntero" en lugar de elegir entre el mouse y el toque.


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

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

  return false
});

77
hmm ... se siente hacky, pero eso podría funcionar. El problema es que en algunos de estos dispositivos, es un retraso notable ... tal vez casi un segundo ... lo que sería molesto para otros en un dispositivo más rápido.
DA.

15
En lugar de usar clickcambiarlo a mousedown... tal vez el gran retraso sea la diferencia entre mousedowny mouseupcuál es la forma en que clickse determina.
Mottie

77
Esta es la solución con la que fui. ¡Gracias! Lo que hago es marcar un elemento touchendaplicando una clase de touched. Esto se dispara antes de que se llame al evento click. Luego agrego un evento de clic que primero verifica la existencia de esa clase. Si está allí, asumimos que se activaron los eventos táctiles y no hacemos nada con un clic que no sea eliminar el nombre de la clase para 'restablecerlo' para la próxima interacción. Si la clase no está allí, entonces asumimos que habían usado un teclado para hacer clic en el elemento y activar la función desde allí.
DA.

77
Debe tener en cuenta que la mayoría de los navegadores de teléfonos tienen un retraso de 300 ms en el evento de clic, por lo que debe aumentar el retraso a al menos 300. Consulte este artículo sobre el problema del retraso de 300 ms . Esto es básicamente para habilitar la funcionalidad de "doble clic para hacer zoom" que era una característica muy importante en los primeros días del iPhone, cuando la mayoría de los sitios web fueron diseñados para ver en una pantalla de escritorio grande.
temor

12
Esta pregunta se ha visto cerca de 100k veces en este momento. Esto parece un caso / problema de uso extraordinariamente común. Sin embargo, esta respuesta y otras sobre esta y otras preguntas similares parecen extravagantes. La solución de Google, aunque está más pensada que la mayoría, parece simplemente un truco más robusto. Para algo tan simple como un controlador de clics, parece que la solución debería ser simple. ¿Nadie ha encontrado una solución no pirateada para este problema?
jinglesthula

73

Esta es la solución que "creo" y saca GhostClick e implementa FastClick. Pruebe por su cuenta y háganos saber si funcionó para usted.

$(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: jsbin.com/ijizat/25 En JavaScript hay una función llamada TouchClick que incorpora lo anterior.
Matt Parkins

55
ALTAMENTE prefiero este método de todas las respuestas a esta pregunta porque es a) no prueba las capacidades del dispositivo yb) no usa setTimeout. En mi experiencia, las soluciones a problemas como estos que usan setTimeout pueden funcionar para la mayoría de los casos, pero el momento de los eventos es bastante arbitrario y específico del dispositivo.
itsmequinn

77
Esto no funcionará porque al pasar 'evento' en la función anónima se convierte en un objeto local dentro de esa función, por lo que event.handledserá igual undefinedcada vez que se active el evento. Aquí hay una solución: establezca el indicador 'manejado' en el elemento de destino mediante jQuery.data().
thdoan

3
Tienes event.stopPropagation();y event.preventDefault();desde mi entendimiento (y prueba), el evento solo se puede disparar una vez con esto. Entonces, ¿por qué incluso verificar if(event.handled !== true)? Para mí no es necesario o me pierdo algo?
nbar

2
¿No se event.handleddebe verificar el valor true? Por lo que puedo decir, nunca es false. Comienza como undefined, y luego desea saber si se ha configurado como true.
ACJ

49

Podrías probar algo como esto:

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

55
Creo que el problema es que está probando las capacidades del dispositivo en lugar de lo que está haciendo el usuario, ¿verdad? El desafío es que debemos admitir dispositivos táctiles que también tengan teclados (para que puedan usar ambos)
DA.

8
Esta no es una buena idea: romperá el evento para un usuario con una pantalla táctil y un mouse cuando use el mouse (las computadoras portátiles con pantallas táctiles se están volviendo bastante comunes).
Kristian J.

1
Excelente artículo relacionado con esto: hacks.mozilla.org/2013/04/…
Luke Melia

Windows 8 siempre conectará el evento 'touchstart' si se usa porque 'ontouchstart' devolverá 'true' para este sistema operativo. Por lo tanto, probablemente no sea la mejor respuesta a menos que su código solo se ejecute en dispositivos con pantalla táctil real, pero si ese es el caso, no necesita la verificación en absoluto.
Neil Monroe

En las computadoras portátiles con pantalla táctil, el usuario no podrá hacer clic en nada usando este código en Chrome (al menos Chrome 46). Sin embargo, IE 11 parece funcionar.
David Sherret

42

Por lo general, esto también funciona:

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

});

8
@ Jonathan está equivocado, si está utilizando versiones más nuevas de jQuery. Live quedó en desuso en la versión 1.7.
Mike Barwick

También probé (y lo leí en otra parte, no es mi idea) con e.stopPropagation (); e.preventDefault (); y funciona (para mí) casi, pero descubrí más tarde que, de hecho, funciona como se predijo 20 veces, pero 1 vez no, por lo que no es a prueba de balas. Mire aquí: stackoverflow.com/questions/25671053/… ¡ Agradezco sus comentarios sobre ese tema!
Garavani

@ MikeBarwick ¿Podría explicar su comentario o dar un enlace que lo explique? Gracias de antemano.
Billal Begueradj

22

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

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

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

Al regresar falsede un controlador de eventos, automáticamente llamará event.stopPropagation()y event.preventDefault(). falseTambién se puede pasar un valor para el controlador como una forma abreviada de function(){ return false; }.


2
funciona muy bien, dispara solo una vez en dispositivos con ambos, esta parece ser la solución menos hacky más limpia para mí
Adam Cooper

Funcionó mejor para mí y realmente tenía sentido por qué.
Amir5000

¿Alguna desventaja de esto? ¿Por qué una solución tan simple no es ampliamente conocida?
ESR

10

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 de clic estaba vinculado a un <a>elemento, así que tuve que eliminar el enlace de clic y volver a vincular un evento de clic que impedía la acción predeterminada para el <a>elemento.

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

  //your code here
});

Desearía que esto funcionara como es, pero no es así. $ (esto) no apunta a lo que crees que hace.
Dave Lowerre

8

Tuve éxito de la siguiente manera.

Pan comido...

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

44
¿No se disparará dos veces en dispositivos que registran un clic y un inicio táctil?
DA.

8
Lo siento, te tengo que saber. Sí, tiene razón: un toque generará un evento de inicio táctil y un evento de clic también. Esto se llama clic fantasma. Google ha implementado una solución para eso. Puede que no sea sencillo, pero funciona perfectamente. Aqui esta el link. code.google.com/mobile/articles/fast_buttons.html#ghost
Hasanavi

2
puede ser trivial pero le falta el eparámetro enfunction()
ProblemsOfSumit

@Hasanavi esta fue una gran solución, sin embargo, encontré mejores resultados usando .on
Neil

1
Gracias @Neil. Sí, usar .on es una mejor idea, ya que podría eliminarse en futuras versiones. He cambiado mi ejemplo de bind a on.
Hasanavi

5

Creo que la mejor práctica es usar ahora:

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

touchend

El evento de toque se desencadena cuando se elimina un punto de contacto de la superficie táctil.

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


mouseup

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

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

EJEMPLO

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


EDITAR (2017)

A partir de 2017, los navegadores que comienzan con Chrome y dan pasos para hacer que el evento de clic sea .on("click")más compatible tanto para el mouse como para el tacto, eliminando el retraso generado por los eventos de toque en las solicitudes de clic.

Esto lleva a la conclusión de que volver a usar solo el evento de clic sería la solución más simple para avanzar.

Todavía no he hecho ninguna prueba de navegador cruzado para ver si esto es práctico.


Esto me pareció prometedor, por lo que jugué un montón, pero finalmente encontré mouseupresultados impredecibles, especialmente cuando uso un trackpad en lugar de un mouse tradicional.
skybondsor

4

Por lo general, no desea mezclar la API predeterminada táctil y no táctil (clic). Una vez que ingresa al mundo táctil, es más fácil tratar solo con las funciones táctiles. A continuación hay un pseudocódigo que haría lo que usted quisiera.

Si se conecta en el evento touchmove y rastrea 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
     }
}

Supongo que ese es el quid de la cuestión: ¿tiene sentido tratar de admitir ambos modelos con una base de código? Actualizaré mi pregunta con algunos escenarios más.
DA.

1
"En general, no desea mezclar el toque predeterminado y el no táctil" después de pasar por esto, estoy de acuerdo. El problema es que siguen haciendo dispositivos táctiles con teclados, lo cual es un dolor de cabeza para los desarrolladores.
DA.

Estoy de acuerdo en que sería mucho más fácil hacer al principio la decisión final js toque o no toque. ¡Qué mundo tan fácil sería! Pero, ¿qué es esto de esos malditos dispositivos que hacen clic y tocan con sensatez? ¿Vale la pena tener todos esos dolores de cabeza debido a ellos? ¿Es este el futuro que me pregunto?
Garavani

No me gusta la idea de tener múltiples definiciones de funciones. Actualmente, mi empresa está trabajando en un proyecto en el que un iPad no se maneja especialmente como dispositivo móvil y aún necesita el evento de presentación. Pero touchend no funciona en las versiones normales de escritorio. Entonces, la solución de @mottie está absolutamente bien para ese escenario.
Pete


3

Bueno ... Todos estos son súper complicados.

Si tienes modernizador, es obvio.

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

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

2
¿Puedes explicar qué hace eso? Creo que comprueba si el dispositivo admite el tacto y, si no, usa el clic. El problema es (o al menos fue , cuando escribí la pregunta) fue que algunos dispositivos son compatibles con ambos. Y en esos dispositivos, todavía necesito manejar ambos eventos, ya que el disparador podría provenir de tocar o hacer clic.
DA.

2
Recomiendo un poco más de explicación.
Aaron Hall

2

Otra implementación para un mejor mantenimiento. Sin embargo, esta técnica también hará event.stopPropagation (). El clic no se detecta en ningún otro elemento que hizo 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()) {
      ...
   }
}

2

Solo para fines de documentación, esto es lo que he hecho para el clic más rápido / más rápido en el escritorio / toque en la solución móvil que se me ocurre:

Reemplacé la onfunción de jQuery por una modificada que, cada vez que el navegador admite eventos táctiles, reemplaza todos mis eventos de clic con 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


1
El problema con esto sería que los dispositivos son compatibles con AMBOS teclados TÁCTILES y donde necesita asegurarse de que está acomodando ambas interacciones.
DA.

Buena observación @DA. Agregué un cheque para reemplazar solo el evento de clic si no está usando ningún evento táctil en combinación con él. Stil no será una solución para cada proyecto, pero estoy seguro de que sería una buena opción para muchos.
Gerson Goulart

2

Se me ocurrió la idea de memorizar si ontouchstartalguna vez se activó. En este caso, estamos en un dispositivo que lo admite y queremos ignorar el onclickevento. Como ontouchstartsiempre debe activarse antes onclick , estoy usando esto:

<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>


1
Dado que los usuarios pueden usar el toque y el mouse durante la misma visita a la página, el problema con este fragmento es que registra el indicador touchAvailable una vez en lugar de por evento. El complemento jQuery de stackoverflow.com/a/26145343/328272 hace lo mismo que su código, pero puede indicar para un evento de mouse si se activó mediante un toque o mouse en función del evento. No solo es decir. al hacer clic, pero también en el mouseleave que podría activarse segundos (o minutos) después del mouseenter (ideal para menús desplegables que deberían expandirse al pasar el mouse con los gestos del mouse o hacer clic cuando se usa el tacto).
lmeurs

2

Podrías intentarlo así:

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

¿qué pasa con los dispositivos que toque el apoyo y el ratón
Walle Cyril

2

En mi caso esto funcionó perfectamente:

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

El principal problema fue cuando en lugar de mouseup probé con clic, en dispositivos táctiles activé clic y toque al mismo tiempo, si uso el clic apagado, alguna funcionalidad no funcionó en absoluto en dispositivos móviles. El problema con el clic es que es un evento global que dispara el resto del evento, incluido el touchend.


1

Esto funcionó para mí, el móvil escucha a ambos, así que evita el uno, que es el evento táctil. El 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]);
  });

1

Siendo para mí la mejor respuesta de Mottie, solo estoy tratando de hacer su código más reutilizable, así que esta 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;
});

0

También estoy trabajando en una aplicación web para Android / iPad, y parece que si solo 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 ha sido sobrecargado por touchstart

Finalmente, puede binb .live ("touchstart", función (e) {e.stopPropagation ();}); para pedirle al evento de inicio táctil que deje de propagarse, haga clic en la sala de estar () para activarse.

Funcionó para mi.


¿Cómo deshabilitarías touchstart?
punkrockbuddyholly

Creo que "podría" hacer algo como: jQuery ("# ​​my_element"). On ('touchstart', function (e) {e.preventDefault ()});
nembleton el

0

Hay muchas cosas a tener en cuenta al intentar 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 activa un clic retrasado, por lo que si sus eventos táctiles modifican la página de alguna manera, el evento de clic se enviará a la nueva versión de la página.


0

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


0

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 tiempos de espera o banderas 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 ontouchstartque se dispara un evento en el elemento, se marca un conjunto y luego se elimina (e ignora), cuando llega el clic.


0

¿Por qué no usar la API de eventos jQuery?

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

He usado este simple evento con éxito. Es limpio, 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
};

1
Se recomienda utilizar la detección de características sobre la detección de agentes de usuario.
jinglesthula

El problema son los dispositivos que admiten tanto el tacto como el clic.
DA.

0

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 ya que quería que se pueda invocar en el siguiente clic / inicio táctil.

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


0

Para funciones simples, simplemente reconozca tocar o hacer clic. Uso 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. Como dije, este es un enfoque simple para eventos de clic / toque.


0

Estoy intentando esto y hasta ahora funciona (pero solo estoy en Android / Phonegap, por lo tanto, advertencia)

  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 los controladores en cada toque, pero todas las cosas consideradas toques no ocurren muy a menudo (¡en tiempo de computadora!)

Tengo una TONELADA de controladores como el del '# teclado', por lo que tener una función simple que me permite lidiar con el problema sin demasiado código es la razón por la que elegí este camino.



0

EDITAR: Mi respuesta anterior (basada en las respuestas en este hilo) no era el camino a seguir para mí. Quería que un submenú se expandiera al entrar con el mouse o al hacer clic con el botón táctil y al contraerse con el botón izquierdo del mouse u otro clic táctil. Dado que los eventos del mouse normalmente se activan después de los eventos táctiles, fue un poco complicado escribir oyentes de eventos que admitan la entrada de la pantalla táctil y del mouse al mismo tiempo.

Complemento jQuery: toque o 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 la compatibilidad con la pantalla táctil y el mouse al mismo tiempo. y separa completamente sus eventos.

De esta forma, el OP puede usar touchstarto touchendpara responder rápidamente a los clics táctiles y clickpara los clics invocados solo con un mouse.

Demostración

Primero hay que hacer ie. el elemento del cuerpo sigue los eventos táctiles:

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

Los eventos del mouse están vinculados a los elementos de la forma predeterminada y al llamar $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 complemento en funcionamiento en http://jsfiddle.net/lmeurs/uo4069nh .

Explicación

  1. Este complemento necesita ser llamado, es decir. el elemento del cuerpo para rastrear touchstarty touchendeventos, de esta manera el touchendevento no tiene que ser disparado en el elemento desencadenante (es decir, un enlace o botón). Entre estos dos eventos táctiles, este complemento considera que cualquier evento del mouse se invoca mediante el tacto.
  2. Los eventos del mouse se activan solo después touchend, cuando se dispara un evento del mouse dentro de ghostEventDelay(opción, 1000ms por defecto) después touchend, este complemento considera que el evento del mouse se invoca mediante el tacto.
  3. Al hacer clic en un elemento usando una pantalla táctil, el elemento gana el :activeestado. El mouseleaveevento solo se dispara después de que el elemento pierde este estado por ej. haciendo clic en otro elemento. Como esto podría ser segundos (¡o minutos!) Después de mouseenterque se haya disparado el evento, este complemento realiza un seguimiento del último mouseenterevento de un elemento : si el último mouseenterevento fue invocado por contacto, el siguiente mouseleaveevento también se considera invocado por contacto.

0

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 activa en la propiedad 'trigger' en el objeto de datos de jQuery que está adjunto 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 sería 'touchstart' seguido de 'click'; sin embargo, el controlador 'clic' no se ejecutará porque "clic" no coincide con el tipo de evento inicial guardado en "disparador" ("inicio táctil").

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 toque nunca se registrará porque el tipo de evento 'disparador' solo se guarda una vez por la carga de la página y el "clic" nunca coincidirían con el "inicio táctil".

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

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

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


¿Por qué usó una propiedad de datos en el documento en lugar de simplemente usar una variable?
Raphael Schweikert

@RaphaelSchweikert Probablemente solo por costumbre. No hay una forma correcta o incorrecta, pero me gusta usar $.data()para almacenar datos a los que pueden acceder múltiples funciones pero que no pertenecen al ámbito global. Una ventaja es que, dado que lo estoy almacenando en un elemento, naturalmente me proporciona más contexto.
thdoan

0

El mejor método que he encontrado es escribir el evento táctil y hacer que ese evento llame al evento de clic normal mediante programación. De esta manera, tiene todos sus eventos de clic normales y luego necesita agregar un solo controlador de eventos para todos los eventos táctiles. Para cada nodo que desee que sea táctil, simplemente agregue la clase "táctil" para invocar el controlador táctil. Con Jquery funciona así con cierta 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
});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.