Empêcher onmouseout lors du survol de l'élément enfant du div absolu parent SANS jQuery


169

J'ai des problèmes avec la onmouseoutfonction dans un div positon absolu. Lorsque la souris frappe un élément enfant dans le div, l'événement mouseout se déclenche, mais je ne veux pas qu'il se déclenche tant que la souris n'est pas hors du div absolu parent.

Comment puis-je empêcher l' mouseoutévénement de se déclencher lorsqu'il frappe un élément enfant SANS jquery.

Je sais que cela a quelque chose à voir avec le bouillonnement d'événements, mais je n'ai pas de chance de savoir comment résoudre ce problème.

J'ai trouvé un article similaire ici: Comment désactiver les événements mouseout déclenchés par des éléments enfants?

Cependant, cette solution utilise jQuery.


J'ai fini par résoudre le problème en utilisant un délai d'expiration et en le nettoyant au survol des éléments enfants
Jean

Salut, j'ai regardé votre démo mais quand je passe en revue les éléments dans le panneau en bas à droite, rien ne se passe?
John

1
J'ai trouvé cette réponse si vous cherchez à empêcher un passage de souris sur l'événement parent lorsque vous passez la souris sur un élément enfant: javascript mouseover / mouseout .
user823527

1
Découvrez la réponse de
craigmichaelmartin

Réponses:


94
function onMouseOut(event) {
        //this is the original element the event handler was assigned to
        var e = event.toElement || event.relatedTarget;
        if (e.parentNode == this || e == this) {
           return;
        }
    alert('MouseOut');
    // handle mouse event here!
}



document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);

J'ai fait une démo rapide de JsFiddle, avec tout le CSS et le HTML nécessaires, regardez-le ...

Lien EDIT FIXED pour la prise en charge de plusieurs navigateurs http://jsfiddle.net/RH3tA/9/

Notez que cela ne vérifie que le parent immédiat, si le div parent avait des enfants imbriqués, vous devez en quelque sorte traverser les éléments parents à la recherche de l '"élément d'origine"

Exemple EDIT pour les enfants imbriqués

EDIT corrigé pour, espérons-le, multi-navigateur

function makeMouseOutFn(elem){
    var list = traverseChildren(elem);
    return function onMouseOut(event) {
        var e = event.toElement || event.relatedTarget;
        if (!!~list.indexOf(e)) {
            return;
        }
        alert('MouseOut');
        // handle mouse event here!
    };
}

//using closure to cache all child elements
var parent = document.getElementById("parent");
parent.addEventListener('mouseout',makeMouseOutFn(parent),true);

//quick and dirty DFS children traversal, 
function traverseChildren(elem){
    var children = [];
    var q = [];
    q.push(elem);
    while (q.length > 0) {
      var elem = q.pop();
      children.push(elem);
      pushAll(elem.children);
    }
    function pushAll(elemArray){
      for(var i=0; i < elemArray.length; i++) {
        q.push(elemArray[i]);
      }
    }
    return children;
}

Et un nouveau lien JSFiddle , EDIT mis à jour


1
Vous devez ajouter un événement mouseout au violon afin qu'il puisse être testé. Une alerte ou quelque chose devrait faire.
John

Désolé, il semble que vous ayez une alerte ('MouseOut'), mais cela ne fonctionne pas pour moi? J'ai frappé run sans option de bibliothèque JS
John

Testé sur safari et chrome, mais devrait fonctionner sur tout! quel est votre navigateur?
Amjad Masad le

2
Et si vous avez de petits enfants? ou arrière-petits-enfants? c'est-à-dire pouvez-vous avoir une solution récursive au lieu de simplement des enfants immédiats?
Roozbeh15

20
Cela ne marche pas. mouseleaveest la bonne réponse
Code Whisperer

126

Utilisez onmouseleave.

Ou, dans jQuery, utilisez mouseleave()

C'est exactement ce que vous recherchez. Exemple:

<div class="outer" onmouseleave="yourFunction()">
    <div class="inner">
    </div>
</div>

ou, dans jQuery:

$(".outer").mouseleave(function(){
    //your code here
});

un exemple est ici .


3
onMouseLeavec'est ce que j'ai fini par utiliser car il ne bouillonne pas.
Alecz

Cela m'a fait gagner beaucoup de temps. thnx
npo

mouseleavene peut pas être annulé.
grecdev le

@grecdev, dites plus!
Ben Wheeler le

98

Pour une solution CSS pure plus simple qui fonctionne dans certains cas ( IE11 ou plus récent ), on pourrait supprimer les enfants en les pointer-eventsdéfinissant surnone

.parent * {
     pointer-events: none;
}

4
Brillant! Je viens de régler mon problème!
Anna_MediaGirl

4
Cela devrait être la première ou au moins la deuxième réponse, j'étais prêt à affronter les tracas des auditeurs d'événements et cela a totalement résolu mon problème. Si simple !
Mickael V.

9
Cela empêche le déclenchement de la souris, mais pour moi, cela empêche également l'enfant réel d'être cliqué en tant que sélection dans le menu, qui est le point du menu.
johnbakers

@hellofunk C'est pourquoi vous appliqueriez l'événement de clic au parent. Le code exact nécessaire varie en fonction de l'implémentation, cette réponse suggère simplement d'utiliser la pointer-eventspropriété
Zach Saucier

1
cela ne fonctionne que si vous n'avez pas d'autres éléments de contrôleur (modifier, supprimer) à l'intérieur de la zone, car cette solution les bloque également ..
Zoltán Süle

28

Voici une solution plus élégante basée sur ce qui suit. il tient compte des événements qui remontent à plus d'un niveau d'enfants. Cela tient également compte des problèmes entre navigateurs.

function onMouseOut(this, event) {
//this is the original element the event handler was assigned to
   var e = event.toElement || event.relatedTarget;

//check for all children levels (checking from bottom up)
while(e && e.parentNode && e.parentNode != window) {
    if (e.parentNode == this||  e == this) {
        if(e.preventDefault) e.preventDefault();
        return false;
    }
    e = e.parentNode;
}

//Do something u need here
}

document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);

Sam Elie, cela semble ne pas fonctionner sur IE, Safari ou Opera. Avez-vous une version multi-navigateurs de ce script? Cela fonctionne comme un charme dans Firefox et Chrome. Merci.

1
Lorsque je colle la fonction ci-dessus dans mon script, Dreamweaver et FF lancent une erreur sur la première ligne, "function onMouseOut (this, event) {". Il semble que vous n'ayez pas le droit de mettre «ceci» comme paramètre. Je suppose que ça marche quand je laisse de côté "this" dans les paramètres d'entrée, mais je ne sais pas avec certitude, parce que "je ne sais pas ce que je ne sais pas".
TARKUS

1
J'ai également eu le même problème dans Chrome sur la même ligne, en excluant thisde la ligne de définition de fonction @GregoryLewis mentionné le problème. Aussi pour plus de support multi-navigateurs, essayez ceci: if (window.addEventListener) {document.getElementById ('parent'). AddEventListener ('mouseout', onMouseOut, true); } else if (window.attachEvent) {document.getElementById ('parent'). attachEvent ("onmouseout", onMouseOut); }
DWils

Très beau! Meilleure réponse si la prise en charge d'anciens navigateurs est requise.
BurninLeo

13

Merci à Amjad Masad qui m'a inspiré.

J'ai la solution suivante qui semble fonctionner dans IE9, FF et Chrome et le code est assez court (sans la fermeture complexe et les choses enfants transversales):

    DIV.onmouseout=function(e){
        // check and loop relatedTarget.parentNode
        // ignore event triggered mouse overing any child element or leaving itself
        var obj=e.relatedTarget;
        while(obj!=null){
            if(obj==this){
                return;
            }
            obj=obj.parentNode;
        }
        // now perform the actual action you want to do only when mouse is leaving the DIV
    }

13

au lieu de onmouseout, utilisez onmouseleave.

Vous ne nous avez pas montré votre code spécifique, je ne peux donc pas vous montrer dans votre exemple spécifique comment le faire.

Mais c'est très simple: remplacez simplement onmouseout par onmouseleave.

C'est tout :) Donc, simple :)

Si vous ne savez pas comment procéder, reportez-vous aux explications sur:

https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmousemove_leave_out

Peace of cake :) Profitez-en :)


C'est un grand cri, qui vaut le détour si vous avez une situation similaire
Chris

12

Si vous utilisez jQuery, vous pouvez également utiliser la fonction "mouseleave", qui s'occupe de tout cela pour vous.

$('#thetargetdiv').mouseenter(do_something);
$('#thetargetdiv').mouseleave(do_something_else);

do_something se déclenchera lorsque la souris entre dans la division cible ou l'un de ses enfants, do_something_else ne se déclenchera que lorsque la souris quittera la division cible et l'un de ses enfants.


8

Je pense que Quirksmode a toutes les réponses dont vous avez besoin (comportement de bouillonnement de différents navigateurs et événements mouseenter / mouseleave ), mais je pense que la conclusion la plus courante de cet événement bouillonnant est l'utilisation d'un cadre comme JQuery ou Mootools (qui a le mouseenter et les événements mouseleave , qui sont exactement ce que vous aviez pressenti).

Jetez un œil à la façon dont ils le font, si vous le souhaitez, faites-le vous
- même ou vous pouvez créer votre version personnalisée "lean mean" de Mootools avec juste la partie événement (et ses dépendances).


7

Essayer mouseleave()

Exemple :

<div id="parent" mouseleave="function">
   <div id="child">

   </div>
</div>

;)


5

J'ai trouvé une solution très simple,

utilisez simplement l'événement onmouseleave = "myfunc ()" plutôt que l'événement onmousout = "myfunc ()"

Dans mon code, cela a fonctionné !!

Exemple:

<html>
<head>
<script type="text/javascript">
   function myFunc(){
      document.getElementById('hide_div').style.display = 'none';
   }
   function ShowFunc(){
      document.getElementById('hide_div').style.display = 'block';
   }
</script>
</head>
<body>
<div onmouseleave="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
   Hover mouse here
   <div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
      CHILD <br/> It doesn't fires if you hover mouse over this child_div
   </div>
</div>
<div id="hide_div" >TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

Même exemple avec la fonction mouseout:

<html>
<head>
<script type="text/javascript">
   function myFunc(){
      document.getElementById('hide_div').style.display = 'none';
   }
   function ShowFunc(){
      document.getElementById('hide_div').style.display = 'block';
   }
</script>
</head>
<body>
<div onmouseout="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
   Hover mouse here
   <div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
      CHILD <br/> It fires if you hover mouse over this child_div
   </div>
</div>
<div id="hide_div">TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

J'espère que ça aide :)



2

Il existe deux façons de gérer cela.

1) Vérifiez le résultat event.target dans votre rappel pour voir s'il correspond à votre div parent

var g_ParentDiv;

function OnMouseOut(event) {
    if (event.target != g_ParentDiv) {
        return;
    }
    // handle mouse event here!
};


window.onload = function() {
    g_ParentDiv = document.getElementById("parentdiv");
    g_ParentDiv.onmouseout = OnMouseOut;
};

<div id="parentdiv">
    <img src="childimage.jpg" id="childimg" />
</div>

2) Ou utilisez la capture d'événements et appelez event.stopPropagation dans la fonction de rappel

var g_ParentDiv;

function OnMouseOut(event) {

    event.stopPropagation(); // don't let the event recurse into children

    // handle mouse event here!
};


window.onload = function() {
    g_ParentDiv = document.getElementById("parentdiv");
    g_ParentDiv.addEventListener("mouseout", OnMouseOut, true); // pass true to enable event capturing so parent gets event callback before children
};

<div id="parentdiv">
    <img src="childimage.jpg" id="childimg" />
</div>

Comment puis-je faire en sorte que cela fonctionne pour onmouseout au lieu de onmouseover?
John

Oups. Ma faute. Je viens de changer toutes les références "mouseover" en "mouseout" dans le code ci-dessus. Cela devrait le faire pour vous.
selbie

Pour la première méthode, event.target correspond toujours à l'élément parent lorsque la souris passe au-dessus de l'élément enfant, donc cela ne fonctionne pas
Jean

La même chose se produit pour la deuxième méthode. Lorsque la souris entre dans un élément enfant, elle déclenche toujours l'événement
Jean

1

Je le fais fonctionner comme un charme avec ceci:

function HideLayer(theEvent){
 var MyDiv=document.getElementById('MyDiv');
 if(MyDiv==(!theEvent?window.event:theEvent.target)){
  MyDiv.style.display='none';
 }
}

Ah, et le MyDivtag est comme ça:

<div id="MyDiv" onmouseout="JavaScript: HideLayer(event);">
 <!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

De cette façon, quand onmouseout va à un enfant, petit-enfant, etc ... le style.display='none'n'est pas exécuté; mais quand onmouseout sort de MyDiv, il s'exécute.

Donc pas besoin d'arrêter la propagation, d'utiliser des minuteries, etc ...

Merci pour les exemples, je pourrais créer ce code à partir d'eux.

J'espère que cela aide quelqu'un.

Peut également être amélioré comme ceci:

function HideLayer(theLayer,theEvent){
 if(theLayer==(!theEvent?window.event:theEvent.target)){
  theLayer.style.display='none';
 }
}

Et puis les balises DIV comme ceci:

<div onmouseout="JavaScript: HideLayer(this,event);">
 <!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

Donc plus général, non seulement pour un div et pas besoin d'ajouter id="..."sur chaque couche.


1

Si vous avez accès à l'élément auquel l'événement est attaché à l'intérieur de la mouseoutméthode, vous pouvez l'utiliser contains()pour voir si le event.relatedTargetest un élément enfant ou non.

Tout event.relatedTargetcomme l'élément dans lequel la souris est passée, s'il ne s'agit pas d'un élément enfant, vous vous êtes éloigné de l'élément.

div.onmouseout = function (event) {
    if (!div.contains(event.relatedTarget)) {
        // moused out of div
    }
}

1

Sur Angular 5, 6 et 7

<div (mouseout)="onMouseOut($event)"
     (mouseenter)="onMouseEnter($event)"></div>

Puis sur

import {Component,Renderer2} from '@angular/core';
...
@Component({
 selector: 'app-test',
 templateUrl: './test.component.html',
 styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit, OnDestroy {
...
 public targetElement: HTMLElement;

 constructor(private _renderer: Renderer2) {
 }

 ngOnInit(): void {
 }

 ngOnDestroy(): void {
  //Maybe reset the targetElement
 }

 public onMouseEnter(event): void {
  this.targetElement = event.target || event.srcElement;
  console.log('Mouse Enter', this.targetElement);
 }

 public onMouseOut(event): void {
  const elementRelated = event.toElement || event.relatedTarget;
  if (this.targetElement.contains(elementRelated)) {
    return;
  }
  console.log('Mouse Out');
 }
}

0

Je vérifie le décalage de l'élément d'origine pour obtenir les coordonnées de page des limites de l'élément, puis je m'assure que l'action de sortie de la souris n'est déclenchée que lorsque la sortie de la souris est en dehors de ces limites. Sale mais ça marche.

$(el).live('mouseout', function(event){
    while(checkPosition(this, event)){
        console.log("mouseovering including children")
    }
    console.log("moused out of the whole")
})

var checkPosition = function(el, event){
    var position = $(el).offset()
    var height = $(el).height()
    var width = $(el).width()
    if (event.pageY > position.top 
|| event.pageY < (position.top + height) 
|| event.pageX > position.left 
|| event.pageX < (position.left + width)){
    return true
}
}

0
var elem = $('#some-id');
elem.mouseover(function () {
   // Some code here
}).mouseout(function (event) {
   var e = event.toElement || event.relatedTarget;
   if (elem.has(e).length > 0) return;

   // Some code here
});

Veuillez donner quelques explications avec votre réponse.
Sebass van Boxel

0

Si vous avez ajouté (ou avez) une classe CSS ou un identifiant à l'élément parent, vous pouvez faire quelque chose comme ceci:

<div id="parent">
  <div>
  </div>
</div>

JavaScript:
document.getElementById("parent").onmouseout = function(e) {
  e = e ? e : window.event //For IE
  if(e.target.id == "parent") {
    //Do your stuff
  }
}

Donc, les choses ne sont exécutées que lorsque l'événement est sur la div parent.


0

Je voulais juste partager quelque chose avec vous.
J'ai eu du mal avec ng-mouseenteret les ng-mouseleaveévénements.

L'étude de cas:

J'ai créé un menu de navigation flottant qui bascule lorsque le curseur se trouve sur une icône.
Ce menu était en haut de chaque page.

  • Pour gérer afficher / masquer dans le menu, je bascule une classe.
    ng-class="{down: vm.isHover}"
  • Pour basculer vm.isHover , j'utilise les événements de souris ng.
    ng-mouseenter="vm.isHover = true"
    ng-mouseleave="vm.isHover = false"

Pour l'instant, tout allait bien et fonctionnait comme prévu.
La solution est propre et simple.

Le problème entrant:

Dans une vue spécifique, j'ai une liste d'éléments.
J'ai ajouté un panneau d'action lorsque le curseur est sur un élément de la liste.
J'ai utilisé le même code que ci-dessus pour gérer le comportement.

Le problème:

J'ai compris que lorsque mon curseur est sur le menu de navigation flottant et également en haut d'un élément, il y a un conflit entre eux.
Le panneau d'action est apparu et la navigation flottante était masquée.

Le fait est que même si le curseur se trouve sur le menu de navigation flottant, l'élément de liste ng-mouseenter est déclenché.
Cela n'a aucun sens pour moi, car je m'attendrais à une interruption automatique des événements de propagation de la souris.
Je dois dire que j'ai été déçu et que je passe du temps à découvrir ce problème.

Premières réflexions:

J'ai essayé d'utiliser ceux-ci:

  • $event.stopPropagation()
  • $event.stopImmediatePropagation()

J'ai combiné beaucoup d'événements de pointeurs ng (mousemove, mouveover, ...) mais aucun ne m'aide.

Solution CSS:

J'ai trouvé la solution avec une simple propriété css que j'utilise de plus en plus:

pointer-events: none;

En gros, je l'utilise comme ça (sur mes éléments de liste):

ng-style="{'pointer-events': vm.isHover ? 'none' : ''}"

Avec celui-ci délicat, les événements ng-mouse ne seront plus déclenchés et mon menu de navigation flottant ne se fermera plus lorsque le curseur sera dessus et sur un élément de la liste.

Pour aller plus loin:

Comme vous vous en doutez, cette solution fonctionne mais je ne l'aime pas.
Nous ne contrôlons pas nos événements et c'est mauvais.
De plus, vous devez avoir accès à la vm.isHoverportée pour y parvenir et cela peut ne pas être possible ou possible mais sale d'une manière ou d'une autre.
Je pourrais faire un violon si quelqu'un veut regarder.

Néanmoins, je n'ai pas d'autre solution ...
C'est une longue histoire et je ne peux pas vous donner une pomme de terre alors pardonnez-moi mon ami.
Quoi qu'il en soit, pointer-events: nonec'est la vie, alors souvenez-vous-en.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.