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:
- No puede detectar una pantalla táctil ( http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ )
- 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:
-
mouseover
siempre va seguido de los eventos de clic que semousedown
mouseup
yclick
, 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/ . - Para la mayoría de los dispositivos / navegadores, el evento
mouseover
siempre está precedido por unpointerover
, su homólogo de MS,MSPointerOver
, otouchstart
- 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:
-
event.preventDefault
seevent.preventDefault
en uno de los eventos táctiles: laevent.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 - 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 touchstart
→ touchend
→ mouseover
→ click
(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:
- La forma de manejar los eventos de mouseover y réplica en la pantalla táctil de Windows 8.1 es acerca de las aplicaciones C # / ASP.Net en Windows, no de páginas web en un navegador
- JQuery .on ("clic") desencadena "mouseover" en el dispositivo táctil es similar, pero se trata de jQuery y la respuesta es un mal enfoque (adivinar una lista codificada de agentes de usuario con pantalla táctil, que se romperían cuando se crearan nuevos dispositivos UA de dispositivo, y que asume falsamente que todos los dispositivos son ratón o pantalla táctil)
- Evitar que el toque genere eventos mouseOver y mouseMove en el navegador de Android es lo más cercano que pude encontrar, pero solo se trata de Android, se trata de evitar no identificar el mouseover al tocar, y no tiene respuesta
- El evento de manejo del mouse con el mouseover para dispositivos táctiles hace que se active el evento de clic incorrecto , pero intentan elumir el patrón de interacción de dos toques de iOS, y también la única respuesta hace ese error de asumir que los toques y el mouse / clics son mutuamente exclusivos .
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 untouchend
o untouchstart
(si el usuario pulsó y mantuvo). - la posición del
mouseover
y eltouchstart
/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 delmouseover
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:
- touchstart
- tocar
- toque
- ratón sobre
- movimiento del ratón
- ratón hacia abajo
- mouseup
- 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.