web-dev-qa-db-fra.com

Mouseleave déclenché par clic

J'ai un div en position absolue et j'essaie de garder trace de la position de la souris dessus et de son départ. Malheureusement, un clic sur le texte dans la zone déclenche parfois l'événement mouseleave. 

DEMO: js fiddle

Comment puis-je empêcher cela?

JS

let tooltip = document.createElement('div');
tooltip.innerHTML = 'HELLO WORLD';
tooltip.setAttribute('class', 'tooltip');
tooltip.style.display = 'none';

tooltip.onclick = evt => {
    console.log('click')
    evt.stopPropagation();
}
tooltip.ondblclick = evt => {
    console.log('double click')
    evt.stopPropagation();
}

tooltip.onmouseenter = () => {
    console.log('tooltip mouse OVER');
}

tooltip.onmouseleave = () => {
    console.log('tooltip mouse OUT')
}

tooltip.style.left = '290px';
tooltip.style.top = '50px';
tooltip.style.display = 'block';
document.body.appendChild(tooltip);

HTML

<div style="width: 300px; height: 300px; background-color: lightblue">

</div>

CSS

.tooltip {
    position: absolute;
    /*display: none;*/
    left: 100;
    top: 100;
    min-width: 80px;
    height: auto;
    background: none repeat scroll 0 0 #ffffff;
    border: 1px solid #6F257F;
    padding: 14px;
    text-align: center;
}
16
Adam Rackis

Cela semble être un bogue (je pourrais le reproduire dans Chrome avec des clics qui font que la souris est abaissée et que la souris est actionnée rapidement après l'autre). 

Je suggérerais de contourner ce problème en vérifiant si la souris survole toujours l'élément au moment où l'événement est déclenché:

tooltip.onmouseleave = (e) => {
    if (tooltip === document.elementFromPoint(e.clientX, e.clientY)) {
        console.log('false positive');
        return;
    }
    console.log('tooltip mouse OUT')
}

L'inconvénient est que lorsque la fenêtre du navigateur perd le focus, cela est également considéré comme un faux positif. Si cela vous pose problème, cochez cette réponse .

9
trincot

J'avais déjà examiné les réponses et les commentaires ici, mais j'ai récemment trouvé un moyen de vérifier si l'événement mouseleave a été déclenché par erreur.

J'ai ajouté un chèque dans mon gestionnaire mouseleave:

private handleMouseLeave(event: MouseEvent) {
    if(event.relatedTarget || event.toElement){
        // do whatever
    }
    // otherwise ignore
}

D'après mes tests sur Chrome v64, ces deux valeurs seront null chaque fois qu'un clic rapide provoque le déclenchement de l'événement mouseleave. La relatedTarget est pour une compatibilité de navigateur plus ancienne

Remarque: ces deux valeurs seront également null si la souris quitte l'élément target et ouvre le navigateur (par exemple, les onglets, menus, etc.) ou quitte la fenêtre du navigateur. Pour mes besoins, ce n’était pas un problème, car c’est un menu glissant avec lequel je travaille, et quitter la fenêtre du navigateur ne devrait pas fermer le menu dans mon cas particulier. 

Note : la dernière version de Firefox (février 2018) semble déclencher mouseleave à chaque clic de mon menu! Devra y regarder

11
Drenai

J'ai aussi rencontré ce bug. Dans mon cas, j'ai ajouté des cases à cocher contenant des étiquettes dans une liste, puis la liste dans un div. J'ai également utilisé des éléments de liste qui étaient des balises <hr>. Si vous cliquez rapidement sur les cases à cocher et les libellés, vous allez parfois déclencher un événement mouseleave sur le diviseur. Cela ne devrait pas se produire car tous les éléments cliqués sont des enfants du div.wrapper.

...
  wrapper.addEventListener(
    'mouseleave',
    (e) => {
      logger('mouseleave fired');
      console.log('mouseleave fired', e);
    },
    false
  );
...

jsfiddle demo

Voici un gif de la reproduction. Cliquez dans la zone d'indice (avec un peu d'intensité et de mouvement), cliquez sur les événements de l'étiquette et les zones de saisie se déclenchent, puis deux événements mouseleave se déclenchent par erreur, puis un troisième lorsque la souris quitte vraiment le bleu surface.

 Sample reproduction

4
Fish Pudding

La réponse de @trincot a presque fonctionné pour moi. Dans mon cas, j'ai affaire à des popovers. Lorsque je clique sur un bouton, un popover apparaît au-dessus du bouton. Donc, document.elementFromPoint(e.clientX, e.clientY) renvoie l'élément popover plutôt que le bouton déclencheur. Voici comment j'ai résolu ceci:

mouseleave(ev: MouseEvent) {
    const trigger: HTMLElement = document.getElementById('#myTrigger');
    const triggerRect = trigger.getBoundingClientRect();
    const falsePositive = isWithingARect(ev.clientX, ev.clientY, triggerRect);

    if (!falsePositive) {
        // do what needs to be done
    }
}

function isWithingARect(x: number, y: number, rect: ClientRect) {
  const xIsWithin = x > rect.left && x < rect.right;
  const yIsWithin = y > rect.top && y < rect.bottom;
  return xIsWithin && yIsWithin;
}
0
Dmitry