web-dev-qa-db-fra.com

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

J'ai des problèmes avec la fonction onmouseout dans un div à position absolue. Lorsque la souris frappe un élément enfant dans la div, l'événement mouseout est déclenché, mais je ne souhaite pas qu'il se déclenche tant que la souris n'est pas en dehors du div, parent absolu.

Comment puis-je empêcher le déclenchement de l'événement mouseout lorsqu'il frappe un élément enfant SANS jQuery.

Je sais que cela a quelque chose à voir avec l'événement bouillonnant, mais je n'ai aucune chance de savoir comment résoudre ce problème.

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

Cependant, cette solution utilise jQuery.

145
John
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, allez voir ça ...

[~ # ~] modifier [~ # ~] lien FIXE pour la prise en charge de plusieurs navigateurs http://jsfiddle.net/RH3tA/ 9 /

[~ # ~] notez [~ # ~] que cela ne fait que vérifier le parent immédiat, si la div du parent avait des enfants imbriqués, vous devez parcourir à travers les éléments parents à la recherche de "l'élément originel"

[~ # ~] éditer un exemple [~ # ~] pour les enfants imbriqués

[~ # ~] edit [~ # ~] Corrigé pour un navigateur croisé

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 JSFiddle , [~ # ~] edit [~ # ~] lien mis à jour

87
Amjad Masad

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 .

95
PHPWannaBePro

Pour une solution CSS pure plus simple qui fonctionne dans certains cas ( IE11 ou plus récent ), vous pouvez supprimer les enfants pointer-events en les fixant à none

.parent * {
     pointer-events: none;
}
84
Zach Saucier

Voici une solution plus élégante basée sur ce qui est venu ci-dessous. cela représente des événements émanant de plus d'un niveau d'enfants. Il prend également en compte les problèmes inter-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);
27
Sam-Elie

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 trucs enfant transversaux):

    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
    }
12
Lawrence Mok

Si vous utilisez jQuery, vous pouvez également utiliser la fonction "Mouseleave", qui gère tout cela pour vous.

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

do_something se déclenche lorsque la souris entre dans thetargetdiv ou l’un de ses enfants, do_something_else ne se déclenche que lorsque la souris quitte thetargetdiv et l’un de ses enfants.

12
Jamie Brown

Essayez mouseleave()

Exemple :

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

   </div>
</div>

;)

7
Jean-Philippe

Je pense que Quirksmode a toutes les réponses dont vous avez besoin (comportement des différents navigateurs et mouseenter/mouseleave des événements), mais je pense que la conclusion la plus courante de cet événement est le désordre bouillonnant est l'utilisation d'un framework tel que JQuery ou Mootools (qui a mouseenter et mouseleave événements, qui sont exactement ce que vous pensiez qu'il se passerait).

Regardez comment ils le font, si vous voulez, faites-le vous-même
ou vous pouvez créer votre version personnalisée "version simplifiée" de Mootools avec uniquement la partie événement (et ses dépendances).

6
Ruben

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

utilisez simplement l'événement onmouseleave = "myfunc ()" à celui 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 :)

5
Arminius

au lieu de onmouseout, utilisez onmouseleave.

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

Mais c’est très simple: remplacez simplement onmouseout par onmouselve.

C'est tout :) Alors, simple :)

Si vous ne savez pas comment faire, voir les explications sur:

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

Paix de gâteau :) Profitez-en :)

4
kavi

Il y a deux façons de gérer cela.

1) Vérifiez le résultat de 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>
1
selbie

Si vous avez accès à l'élément auquel l'événement est attaché dans la méthode mouseout, vous pouvez utiliser contains() pour voir si le event.relatedTarget est un élément enfant ou non.

Comme event.relatedTarget est l'élément dans lequel la souris est passée; s'il ne s'agit pas d'un élément enfant, vous avez supprimé l'élément.

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

On 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');
 }
}
1
Alan Torrico

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 la balise MyDiv ressemble à ceci:

<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, un 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 faire ce code d'eux.

J'espère que ça 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, pas seulement pour une div et pas besoin d’ajouter id="..." sur chaque couche.

1
z666zz666z

Bien que la solution à laquelle vous faites référence utilise jquery, mouseenter et mouseleave sont des événements nat nat, vous pouvez donc utiliser sans jquery.

1
Mohammed Essehemy
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
});
0
Michael Vaganov

Je vérifie le décalage de l'élément d'origine pour obtenir les coordonnées de la page des limites de l'élément, puis je m'assure que l'action mouseout n'est déclenchée que lorsque la souris est hors 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
saranicole

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

L’étude de cas:

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

  • Pour gérer afficher/masquer 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 a fonctionné 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'actions lorsque le curseur est placé 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 découvert que lorsque mon curseur se trouvait dans le menu de navigation flottant et également au-dessus d'un élément, il y avait 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 rupture 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 pensées:

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

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

J'ai combiné beaucoup d'événements de pointeur 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 les éléments de ma liste):

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

Avec cette situation délicate, 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 sur lui 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 un accès à la portée de vm.isHover Pour y parvenir. Ce n'est peut-être pas possible ou possible, mais sale d'une manière ou d'une autre.
Je pourrais faire du violon si quelqu'un voulait regarder.

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

0
C0ZEN

Si vous avez ajouté (ou avez) une classe ou un identifiant CSS à 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 parente.

0
Karthik Palaniappan