vanilla over evento event javascript cross-browser touch raphael

evento - onmouseout onmouseover javascript



¿Cómo identificar si un objeto de evento de mouseover proviene de una pantalla táctil? (5)

Dada la complejidad del problema, pensé que valía la pena detallar los problemas y los casos clave involucrados en cualquier solución potencial.

Los problemas:

1 - Diferentes implementaciones de eventos táctiles en dispositivos y navegadores . Lo que funciona para algunos definitivamente no funcionará para otros. Solo necesita echar un vistazo a los recursos de patrickhlauke para tener una idea de qué tan diferente es el proceso de tocar una pantalla táctil que se maneja actualmente en los dispositivos y navegadores.

2 - El controlador de eventos no da ninguna pista sobre su activación inicial. También tiene toda la razón al decir que el objeto de event es idéntico (ciertamente en la gran mayoría de los casos) entre los eventos del mouse enviados mediante la interacción con un mouse, y los eventos del mouse distribuidos mediante una interacción táctil.

3 - Cualquier solución a este problema que cubra todos los dispositivos podría durar poco tiempo, ya que las Recomendaciones actuales del W3C no tienen suficientes detalles sobre cómo deben manejarse los eventos de toque / clic ( https://www.w3.org/TR/touch-events/ ), por lo que los navegadores continuarán teniendo implementaciones diferentes. También parece que el documento de los estándares de Touch Events no ha cambiado en los últimos 5 años, por lo que esto no se solucionará pronto. https://www.w3.org/standards/history/touch-events

4 - Idealmente, las soluciones no deberían usar los tiempos de espera, ya que no hay un tiempo definido desde el evento táctil al evento del mouse y, dada la especificación, es probable que no haya ningún momento pronto. Desafortunadamente, los tiempos muertos son casi inevitables, como explicaré más adelante.

Una solución de futuro:

En el futuro, la solución probablemente será usar Pointer Events lugar de Pointer Events de mouse / touch ya que estos nos dan el tipo de pointerType ( https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events ), pero desafortunadamente, aún no hemos llegado a un estándar establecido, por lo que la compatibilidad entre navegadores ( https://caniuse.com/#search=pointer%20events ) es deficiente.

¿Cómo resolvemos esto en este momento?

Si aceptamos eso:

  1. No puede detectar una pantalla táctil ( http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ )
  2. Incluso si pudiéramos, todavía existe el problema de los eventos no táctiles en una pantalla táctil

Entonces solo podemos usar datos sobre el evento del mouse en sí para determinar su origen. Como hemos establecido, el navegador no proporciona esto, por lo que debemos agregarlo nosotros mismos. La única forma de hacerlo es mediante los eventos táctiles que se activan aproximadamente al mismo tiempo que el evento del mouse.

Mirando nuevamente los recursos de patrickhlauke , podemos hacer algunas declaraciones:

  1. mouseover siempre va seguido de los eventos de clic que se mousedown mouseup y click , siempre en ese orden. (A veces separados por otros eventos). Esto está respaldado por las recomendaciones del W3C: https://www.w3.org/TR/touch-events/ .
  2. Para la mayoría de los dispositivos / navegadores, el evento mouseover siempre está precedido por un pointerover , su homólogo de MS, MSPointerOver , o touchstart
  3. Los dispositivos / navegadores cuyo orden de eventos comienza con el mouseover deben ignorarse. No podemos establecer que el evento del mouse fue activado por un evento táctil antes de que el evento táctil se haya activado.

Dado esto, podríamos establecer un indicador durante el pointerover , MSPointerOver y touchstart , y eliminarlo durante uno de los eventos de clic. Esto funcionaría bien, excepto por un puñado de casos:

  1. event.preventDefault se event.preventDefault en uno de los eventos táctiles: la event.preventDefault nunca se desactivará ya que no se llamarán los eventos de clic, por lo que cualquier evento de clic genuino futuro sobre este elemento aún se marcará como un evento de toque
  2. Si el elemento de destino se mueve durante el evento. El estado de las recomendaciones del W3C

Si el contenido del documento ha cambiado durante el procesamiento de los eventos táctiles, entonces el agente de usuario puede enviar los eventos del mouse a un destino diferente al de los eventos táctiles.

Desafortunadamente esto significa que siempre tendremos que usar tiempos de espera. Que yo sepa, no hay forma de establecer cuándo un evento táctil ha llamado event.preventDefault , ni comprender cuándo el elemento táctil se ha movido dentro del DOM y el evento de clic activado en otro elemento.

Creo que este es un escenario fascinante, por lo que esta respuesta se modificará en breve para contener una respuesta de código recomendada. Por ahora, recomendaría la respuesta proporcionada por @ibowankenobi o la respuesta proporcionada por @Manuel Otto.

En prácticamente todos los navegadores actuales ( detalles extensos de patrickhlauke en github , que resumí en una respuesta SO , y también más información de QuirksMode ), la pantalla táctil activa eventos de mouseover (a veces creando un seudo cursor invisible que permanece donde el usuario toca hasta se tocan en otro lado).

A veces esto causa un comportamiento indeseable en casos en los que tocar / hacer clic y pasar el mouse tienen la intención de hacer cosas diferentes.

Desde dentro de una función que responde a un evento de mouseover, al que se le ha pasado el objeto de event , hay alguna manera de que pueda verificar si se trata de un mouseover "real" desde un cursor en movimiento que se movió desde afuera de un elemento hasta dentro de él, o si fue causado por este comportamiento de la pantalla táctil de una pantalla táctil?

El objeto de event ve idéntico. Por ejemplo, en Chrome, un evento de mouseover causado por un usuario que toca una pantalla táctil tiene el type: "mouseover" y no veo nada que lo identifique como relacionado con el toque.

Tuve la idea de vincular un evento para touchstart de forma touchstart que altera los eventos de mouseover y luego un evento para touchend que elimina esta alteración. Desafortunadamente, esto no funciona, porque el orden de los eventos parece ser touchstarttouchendmouseoverclick (no puedo adjuntar la función normalizar-mouseover para hacer clic sin estropear otra funcionalidad).

Esperaba que esta pregunta hubiera sido formulada antes, pero las preguntas existentes no son suficientes:

Lo mejor que se me ocurre es tener un evento táctil que establezca un indicador variable accesible globalmente como, por ejemplo, window.touchedRecently = true; en touchstart pero no haga clic, luego elimina esta bandera después de, por ejemplo, un setTimeout 500 setTimeout . Este es un truco feo sin embargo.

Nota: no podemos asumir que los dispositivos de pantalla táctil no tengan un cursor errante como el del ratón o viceversa, porque hay muchos dispositivos que usan una pantalla táctil y un lápiz similar al mouse que mueve el cursor mientras se desplaza cerca de la pantalla, o que usa una pantalla táctil y un ratón (por ejemplo, portátiles con pantalla táctil). Más detalles en mi respuesta a ¿Cómo detecto si un navegador admite eventos de mouseover? .

Nota n. ° 2: esta no es una pregunta de jQuery, mis eventos provienen de las rutas de Raphael.js para las que jQuery no es una opción y dan un objeto de event navegador simple. Si hay una solución específica de Raphael, aceptaría eso, pero es muy improbable y una solución de JavaScript simple sería mejor.


Lo que sí sabemos es:

Cuando el usuario no usa el mouse.

  • El mouseover se dispara directamente (dentro de 800 ms) después de un touchend o un touchstart (si el usuario pulsó y mantuvo).
  • la posición del mouseover y el touchstart / touchend son idénticos.

Cuando el usuario usa un mouse / lápiz

  • El mouseover se dispara antes de los eventos táctiles, incluso si no, la posición del mouseover no coincidirá con la posición de los eventos táctiles el 99% del tiempo.

Teniendo en cuenta estos puntos, hice un fragmento, que agregará una bandera triggeredByTouch = true al evento si se cumplen las condiciones enumeradas. Además, puede agregar este comportamiento a otros eventos del mouse o establecer kill = true para descartar los eventos del mouse activados al tocarlos por completo.

(function (target){ var keep_ms = 1000 // how long to keep the touchevents var kill = false // wether to kill any mouse events triggered by touch var touchpoints = [] function registerTouch(e){ var touch = e.touches[0] || e.changedTouches[0] var point = {x:touch.pageX,y:touch.pageY} touchpoints.push(point) setTimeout(function (){ // remove touchpoint from list after keep_ms touchpoints.splice(touchpoints.indexOf(point),1) },keep_ms) } function handleMouseEvent(e){ for(var i in touchpoints){ //check if mouseevent''s position is (almost) identical to any previously registered touch events'' positions if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){ //set flag on event e.triggeredByTouch = true //if wanted, kill the event if(kill){ e.cancel = true e.returnValue = false e.cancelBubble = true e.preventDefault() e.stopPropagation() } return } } } target.addEventListener(''touchstart'',registerTouch,true) target.addEventListener(''touchend'',registerTouch,true) // which mouse events to monitor target.addEventListener(''mouseover'',handleMouseEvent,true) //target.addEventListener(''click'',handleMouseEvent,true) - uncomment or add others if wanted })(document)

Pruébalo:

function onMouseOver(e){ console.log(''triggered by touch:'',e.triggeredByTouch ? ''yes'' : ''no'') } (function (target){ var keep_ms = 1000 // how long to keep the touchevents var kill = false // wether to kill any mouse events triggered by touch var touchpoints = [] function registerTouch(e){ var touch = e.touches[0] || e.changedTouches[0] var point = {x:touch.pageX,y:touch.pageY} touchpoints.push(point) setTimeout(function (){ // remove touchpoint from list after keep_ms touchpoints.splice(touchpoints.indexOf(point),1) },keep_ms) } function handleMouseEvent(e){ for(var i in touchpoints){ //check if mouseevent''s position is (almost) identical to any previously registered touch events'' positions if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){ //set flag on event e.triggeredByTouch = true //if wanted, kill the event if(kill){ e.cancel = true e.returnValue = false e.cancelBubble = true e.preventDefault() e.stopPropagation() } return } } } target.addEventListener(''touchstart'',registerTouch,true) target.addEventListener(''touchend'',registerTouch,true) // which mouse events to monitor target.addEventListener(''mouseover'',handleMouseEvent,true) //target.addEventListener(''click'',handleMouseEvent,true) - uncomment or add others if wanted })(document)

a{ font-family: Helvatica, Arial; font-size: 21pt; }

<a href="#" onmouseover="onMouseOver(event)">Click me</a>


Por lo general, tengo un par de esquemas generales que uso para esto, uno de ellos usa un principio manual de setTimeout para activar una propiedad. Explicaré esto aquí, pero primero trate de razonar sobre el uso de touchstart, touchmove y touchend en dispositivos táctiles y use el mouseover en el destop.

Como ya sabe, llamar a event.preventDefault (el evento no debe ser pasivo para que esto funcione con touchstart) en ninguno de los eventos táctiles cancelará las llamadas al ratón subsiguientes para que no tenga que lidiar con ellos. Pero en caso de que esto no sea lo que quieres, esto es lo que uso algunas veces (me refiero como "biblioteca" a tu biblioteca de manipulación de dom, y "elem" como tu elemento):

con setTimeout

library.select(elem) //select the element .property("_detectTouch",function(){//add a _detectTouch method that will set a property on the element for an arbitrary time return function(){ this._touchDetected = true; clearTimeout(this._timeout); this._timeout = setTimeout(function(self){ self._touchDetected = false;//set this accordingly, I deal with either touch or desktop so I can make this 10000. Otherwise make it ~400ms. (iOS mouse emulation delay is around 300ms) },10000,this); } }).on("click",function(){ /*some action*/ }).on("mouseover",function(){ if (this._touchDetected) { /*coming from touch device*/ } else { /*desktop*/ } }).on("touchstart",function(){ this._detectTouch();//the property method as described at the beginning toggleClass(document.body,"lock-scroll",true);//disable scroll on body by overflow-y hidden; }).on("touchmove",function(){ disableScroll();//if the above overflow-y hidden don''t work, another function to disable scroll on iOS. }).on("touchend",function(){ library.event.preventDefault();//now we call this, if you do this on touchstart chrome will complain (unless not passive) this._detectTouch(); var touchObj = library.event.tagetTouches && library.event.tagetTouches.length ? library.event.tagetTouches[0] : library.event.changedTouches[0]; if (elem.contains(document.elementFromPoint(touchObj.clientX,touchObj.clientY))) {//check if we are still on the element. this.click();//click will never be fired since default prevented, so we call it here. Alternatively add the same function ref to this event. } toggleClass(document.body,"lock-scroll",false);//enable scroll enableScroll();//enableScroll })

Otra opción sin setTimeout es pensar que mousover es contrario a touchstart y mouseout contador a touchend. Por lo tanto, los eventos anteriores (los eventos táctiles) establecerán una propiedad, si los eventos del mouse detectan esa propiedad, entonces no se activarán y restablecerán la propiedad a su valor inicial y así sucesivamente. En ese caso algo así también hará:

sin setTimeout

.... .on("mouseover",function(dd,ii){ if (this._touchStarted) {//touch device this._touchStarted = false;//set it back to false, so that next round it can fire incase touch is not detected. return; } /*desktop*/ }) .on("mouseout",function(dd,ii){//same as above if(this._touchEnded){ this._touchEnded = false; return; } }) .on("touchstart",function(dd,ii){ this._touchStarted = true; /*some action*/ }) .on("touchend",function(dd,ii){ library.event.preventDefault();//at this point emulations should not fire at all, but incase they do, we have the attached properties this._touchEnded = true; /*some action*/ });

Quité muchos detalles pero supongo que esta es la idea principal.


Puedes usar modernizr para eso! Acabo de probar esto en un servidor de desarrollo local y funciona.

if (Modernizr.touch) { console.log(''Touch Screen''); } else { console.log(''No Touch Screen''); }

¿Entonces empezaría allí?


Según https://www.html5rocks.com/en/mobile/touchandmouse/
Para un solo clic el orden de los eventos es:

  1. touchstart
  2. tocar
  3. toque
  4. ratón sobre
  5. movimiento del ratón
  6. ratón hacia abajo
  7. mouseup
  8. hacer clic

Por lo tanto, es posible que pueda establecer un valor booleano arbitrario isFromTouchEvent = true; en onTouchStart () y isFromTouchEvent = false; en onClick () y verifique eso dentro de onMouseOver (). Esto no funciona muy bien, ya que no se garantiza que todos estos eventos se encuentren en el elemento que estamos tratando de escuchar.