Comment déterminer si un objet de l'événement mouseover provient d'une touche d'écran tactile?
sur pratiquement tous les navigateurs actuels ( détails de patrickhlauke sur github , qui j'ai résumé dans une réponse SO , et aussi un peu plus d'info de QuirksMode ), touche écran déclencheur mouseover
événements (créant parfois un pseudo-curseur invisible qui reste où l'utilisateur touché jusqu'à ce qu'ils touchent ailleurs).
parfois, cela provoque un comportement indésirable dans les cas où touch / click et mouseover sont destinés à faire des choses différentes.
de l'intérieur d'une fonction répondant à un événement mouseover, qui a passé l'objet event
, est-ce que je peux vérifier s'il s'agit d'un" vrai " mouseover à partir d'un curseur en mouvement qui s'est déplacé de l'extérieur à l'intérieur d'un élément, ou s'il a été causé par ce comportement d'écran tactile à partir d'un toucher d'écran tactile?
l'objet event
semble identique. Exemple, sur chrome, un événement mouseover causé par un utilisateur qui touche un écran tactile a type: "mouseover"
et rien que je puisse voir qui pourrait l'identifier comme lié au toucher.
j'ai eu l'idée de lier un événement à touchstart
qui modifie les événements mouseover puis un événement à touchend
qui supprime cette modification. Malheureusement, cela ne fonctionne pas, parce que l'ordre d'événement semble être touchstart
→ touchend
→ mouseover
→ click
(Je ne peux pas attacher le relooking normal fonction pour cliquer sans gâcher d'autres fonctionnalités).
Je m'attendais à ce que cette question ait déjà été posée, mais les questions existantes ne suffisent pas:
- Comment gérer la souris et mouseleave dans Windows 8.1 écran Tactile est sur C# / ASP.Net des applications sur Windows, pas de pages web dans un navigateur
- JQuery .("cliquez sur") déclencheurs "mouseover" sur le dispositif tactile est similaire, mais est sur jQuery et la réponse est une mauvaise approche (deviner une liste codée dur des agents utilisateurs d'écran tactile, qui se briserait lorsque de nouveaux dispositifs UAs sont créés, et qui suppose faussement tous les dispositifs sont souris ou écran tactile)
- empêcher le contact de générer des événements mouseOver et mouseMove dans le navigateur Android est le plus proche que j'ai pu trouver, mais il est seulement à propos de Android, est sur la prévention de ne pas identifier mouseover sur le toucher, et n'a pas de réponse
- le navigateur manipulant l'événement mouseover pour les appareils tactiles provoque l'événement de clic erroné de tirer est lié, mais ils essaient d'élumater le modèle d'interaction à deux claquements iOS, et aussi la seule réponse fait cette erreur de supposer que les touches et la souris/clics sont mutuellement exclusifs.
le mieux que je puisse penser est d'avoir un événement tactile qui fixe un drapeau de variable globalement accessible comme, par exemple, window.touchedRecently = true;
sur touchstart
mais ne clique pas, puis supprime ce drapeau après, disons, un 500ms setTimeout
. C'est un vilain hack.
Note - Nous ne peut pas supposer que les dispositifs d'écran tactile n'ont pas de curseur mobile semblable à une souris ou vice versa, parce qu'il y a de nombreux dispositifs qui utilisent un écran tactile et un stylo mobile qui déplace un curseur en vol stationnaire près de l'écran, ou qui utilisent un écran tactile et une souris (p. ex. Ordinateurs portables à écran tactile). Plus de détails dans ma réponse à Comment puis-je détecter si un navigateur prend en charge les événements mouseover? .
Note #2 - c'est pas une question de jQuery, Mes événements viennent de Raphael.js paths pour lequel jQuery n'est pas une option et qui donne un simple objet de navigateur event
. S'il y a une solution spécifique à Raphaël, Je l'accepterais, mais c'est très peu probable et une solution javascript brut serait mieux.
5 réponses
ce que nous savons est:
lorsque l'utilisateur n'utilise pas de souris
- le
mouseover
est tiré directement (à moins de 800ms) après untouchend
ou untouchstart
(si l'utilisateur a tapé et tenue). - la position du
mouseover
et dutouchstart
/touchend
sont identiques.
Lorsque l'utilisateur utilise une souris/stylet
- le
mouseover
est déclenché avant les évènements de contact, même si ce n'est pas le cas, la position dumouseover
ne correspond pas à la position des évènements de contact 99% du temps.
en gardant ces points à l'esprit, j'ai fait un extrait, qui ajoutera un drapeau triggeredByTouch = true
à l'événement si les conditions énumérées sont remplies. En outre, vous pouvez ajouter ce comportement à d'autres événements de la souris ou Définir kill = true
afin de rejeter les événements de souris déclenchés par le toucher complètement.
(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)
l'Essayer:
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>
étant donné la complexité de la question, j'ai pensé qu'il valait la peine de détailler les questions et les cas de contours impliqués dans toute solution potentielle.
Les questions:
1 - différentes implémentations d'événements tactiles à travers les appareils et les navigateurs . Ce qui fonctionne pour certains ne fonctionnera certainement pas pour d'autres. Il suffit de jeter un coup d'oeil à ces patrickhlauke ressources pour se faire une idée de la façon différente dont le processus de tapoter un écran tactile est actuellement traitée à travers les appareils et les navigateurs.
2 - le gestionnaire d'événements ne donne aucun indice quant à son déclencheur initial. vous avez aussi tout à fait raison de dire que l'objet event
est identique (certainement dans la grande majorité des cas) entre les événements de souris envoyés par interaction avec une souris, et les événements de souris distribué par une interaction tactile.
3 - N'importe quelle solution à ce problème qui couvre tous les dispositifs pourrait bien être de courte durée comme les recommandations actuelles du W3C ne vont pas dans assez de détails sur la façon dont les événements de contact/clic devraient être manipulés ( https://www.w3.org/TR/touch-events / ), de sorte que les navigateurs continueront d'avoir différentes implémentations. Il semble également que le document sur les normes relatives aux événements tactiles n'a pas changé au cours des 5 dernières années, donc cela ne va pas se réparer de sitôt. https://www.w3.org/standards/history/touch-events
4 - idéalement, les solutions ne devraient pas utiliser les temps morts car il n'y a pas de temps défini entre l'événement tactile et l'événement de la souris, et étant donné les spécifications, il n'y aura probablement pas de temps avant. Malheureusement, les temps morts sont presque inévitables, comme je l'expliquerai plus tard.
une solution future:
à l'avenir, la solution sera probablement d'utiliser Pointer Events
au lieu d'événements souris / toucher comme ceux-ci nous donnent le pointerType
( https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events ), mais malheureusement nous ne sommes pas encore là en termes de norme établie, et donc cross-browser compatibilité ( ) https://caniuse.com/#search=pointer%20events ) est pauvre.
comment résoudre ce problème à l'heure actuelle
si nous acceptons cela:
- vous ne pouvez pas détecter un écran tactile ( http://www.stucox.com/blog/you-cant-detect-a-touchscreen / )
- même si nous pouvions, il y a toujours le problème d'événements non tactiles sur un écran tactile
alors nous ne pouvons utiliser des données sur l'événement de la souris elle-même pour déterminer son origine. Comme nous l'avons établi, le navigateur ne fournit pas cela, donc nous devons l'Ajouter nous-mêmes. La seule façon de le faire est d'utiliser la touche événements qui sont déclenchés en même temps que l'événement de la souris.
en regardant les patrickhlauke encore une fois, nous pouvons faire quelques déclarations:
-
mouseover
est toujours suivi par les clicsmousedown
mouseup
etclick
- toujours dans cet ordre. (Parfois séparés par d'autres événements). Ceci est confirmé par les recommandations du W3C: https://www.w3.org/TR/touch-events / . - pour la plupart des appareils / navigateurs, l'événement
mouseover
est toujours précédé parpointerover
, son équivalent MSMSPointerOver
, outouchstart
- les appareils / navigateurs dont l'ordre d'événement commence par
mouseover
doivent être ignorés. Nous ne pouvons pas établir que l'événement de la souris a été déclenché par un événement tactile avant l'événement tactile lui-même a été déclenché.
compte tenu de cela, nous pourrions placer un drapeau pendant pointerover
, MSPointerOver
, et touchstart
, et l'enlever pendant l'un des événements de clic. Cela fonctionnerait bien, sauf pour une poignée de cas:
-
event.preventDefault
est appelé sur l'un des événements touch - le drapeau ne sera jamais désactivé car les événements click ne seront pas appelés, et donc tout futur événement click véritable sur cet élément serait encore marqué comme un événement touch - si l'élément cible est déplacé lors de l'événement. Les recommandations du W3C indiquent
si le contenu du document a changé lors du traitement de la toucher les événements, puis l'agent utilisateur peut envoyer les événements de la souris à un autre cible que les événements tactiles.
malheureusement, cela signifie que nous aurons toujours besoin d'utiliser des temps morts. À ma connaissance , il n'y a aucun moyen d'établir quand un événement tactile a appelé event.preventDefault
, ni de comprendre quand l'élément tactile a été déplacé dans le DOM et que l'événement de clic a été déclenché sur un autre élément.
je pense que c'est un scénario fascinant, donc cette réponse sera modifiée sous peu pour contenir une réponse code recommandé. Pour l'instant, je recommande la réponse de @ibowankenobi ou celle de @Manuel Otto.
selon https://www.html5rocks.com/en/mobile/touchandmouse /
pour un simple clic l'ordre des événements est:
- évènements touchstart
- touchmove
- touchend
- mouseover
- mousemove
- mousedown
- mouseup
- , cliquez sur
ainsi vous pourriez être en mesure de définir un booléen arbitraire isFromTouchEvent = true;
dans onTouchStart() et isFromTouchEvent = false;
dans onClick() et de vérifier l'intérieur de onMouseOver(). Cela ne fonctionne pas très bien car nous ne sommes pas sûrs d'avoir tous ces événements dans l'élément que nous essayons d'écouter.
vous pouvez utiliser modernizr pour cela! Je viens de le tester sur un serveur de développement local et ça marche.
if (Modernizr.touch) {
console.log('Touch Screen');
} else {
console.log('No Touch Screen');
}
donc je commencerais par là?
j'ai habituellement quelques schémas généraux que j'utilise pour cela, l'un d'eux utilise un principe manuel de setTimeout pour déclencher une propriété. Je vais expliquer celui-ci ici, mais d'abord essayer de raisonner sur l'utilisation de touchstart, touchmove et touchend sur les appareils tactiles et utiliser mouseover sur destop.
comme vous le savez, événement appelant.preventDefault (l'événement ne doit pas être passif pour que cela fonctionne avec touchstart) dans l'un des événements touchevents annulera les appels à souris suivants ainsi vous n'avez pas besoin de traiter avec eux. Mais au cas où ce n'est pas ce que vous voulez, voici ce que j'utilise parfois (je me réfère comme "bibliothèque" à votre bibliothèque de manipulation dom, et "elem" comme votre élément):
avec 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
})
une autre option sans setTimeout est de penser que mousover est contre touchstart et mouseout contre touchend. Si l'ancien événements (les événements tactiles) permettra de définir une propriété, si les événements de souris détecter la propriété, alors qu'ils ne pas de feu et de réinitialiser la propriété à sa valeur initiale, et ainsi de suite. Dans ce cas, quelque chose de ce genre fera aussi l'affaire:
sans 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*/
});
j'ai enlevé beaucoup de détails, mais je suppose que c'est l'idée principale.