Zapobiegaj onmouseout podczas najechania na element podrzędny nadrzędnego absolutnego div BEZ jQuery

169

Mam problem z onmouseoutfunkcją w absolutnie pozycjonowanym div. Gdy mysz uderza w element podrzędny w div, uruchamia się zdarzenie mouseout mouseout, ale nie chcę, aby było uruchamiane, dopóki mysz nie znajdzie się poza nadrzędnym, absolutnym div.

Jak mogę zapobiec mouseouturuchamianiu zdarzenia, gdy trafi ono w element podrzędny BEZ jquery.

Wiem, że ma to coś wspólnego z bulgotaniem wydarzeń, ale nie mam szczęścia, aby dowiedzieć się, jak to rozwiązać.

Znalazłem podobny post tutaj: Jak wyłączyć zdarzenia mouseout wyzwalane przez elementy potomne?

Jednak to rozwiązanie wykorzystuje jQuery.

Jan
źródło
Skończyło się na rozwiązywaniu problemu, korzystając timeout i wyczyszczenie go przy aktywowaniu elementów potomnych
John
Cześć, obejrzałem twoje demo, ale kiedy przejdę przez elementy w prawym dolnym panelu, nic się nie dzieje?
John
1
Znalazłem tę odpowiedź, jeśli chcesz zapobiec przesunięciu myszy na zdarzenie nadrzędne po najechaniu kursorem myszy na element podrzędny: javascript mouseover / mouseout .
user823527
1
Sprawdź odpowiedź @Zach Saucier pochowaną poniżej
Craigmichaelmartin

Odpowiedzi:

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);

Zrobiłem szybkie demo JsFiddle, ze wszystkimi potrzebnymi CSS i HTML, sprawdź to ...

EDYTUJ STAŁE łącze do obsługi wielu przeglądarek http://jsfiddle.net/RH3tA/9/

PAMIĘTAJ, że to sprawdza tylko bezpośredniego rodzica, jeśli nadrzędny element div miał zagnieżdżone dzieci, musisz w jakiś sposób przejść przez elementy, które rodzice szukają „Elementu oryginalnego”

Przykład EDYCJI dla zagnieżdżonych dzieci

EDYCJA Naprawiono, miejmy nadzieję, w różnych przeglądarkach

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;
}

I nowy JSFiddle , zaktualizowany link EDIT

Amjad Masad
źródło
1
Powinieneś dodać zdarzenie mouseout do skrzypiec, aby można było je przetestować. Alert czy coś powinno zrobić.
John
Przepraszam, wygląda na to, że masz alert („MouseOut”), ale to nie działa dla mnie? Udało mi się uruchomić bez opcji biblioteki JS
John
Testowane na Safari i Chrome, ale powinno działać na wszystkim! jaka jest twoja przeglądarka
Amjad Masad,
2
A co jeśli masz wnuki? czy prawnuki? tj. czy możesz mieć rozwiązanie rekurencyjne zamiast tylko immidiate dzieci?
Roozbeh 15
20
To nie działa. mouseleaveto poprawna odpowiedź
Code Whisperer,
126

Użyj onmouseleave.

Lub w jQuery użyj mouseleave()

To jest dokładnie to, czego szukasz. Przykład:

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

lub w jQuery:

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

przykład jest tutaj .

PHPWannaBePro
źródło
3
onMouseLeavejest tym, czego ostatecznie użyłem, ponieważ nie pęka.
Alecz
Zaoszczędziło mi mnóstwo czasu. thnx
npo
mouseleavenie można anulować.
grecdev
@grecdev, powiedz więcej!
Ben Wheeler
97

Aby uzyskać prostsze, czyste rozwiązanie CSS, które działa w niektórych przypadkach ( IE11 lub nowsze ), można usunąć dzieci, pointer-eventsustawiając je nanone

.parent * {
     pointer-events: none;
}
Zach Saucier
źródło
4
Znakomity! Całkowicie właśnie rozwiązałem mój problem!
Anna_MediaGirl
4
To powinna być pierwsza lub co najmniej druga odpowiedź, byłem gotowy na kłopoty ze słuchaczami wydarzeń, a to całkowicie rozwiązało również mój problem. Tak prosty !
Mickael V.
9
Zapobiega to uruchamianiu myszy, ale dla mnie zapobiega również kliknięciu rzeczywistego dziecka jako wyboru w menu, które jest punktem menu.
johnbakers
@hellofunk Dlatego zastosowałbyś zdarzenie click do rodzica. Dokładny wymagany kod różni się w zależności od implementacji, ta odpowiedź sugeruje tylko użycie pointer-eventswłaściwości
Zach Saucier
1
działa tylko wtedy, gdy nie masz innych elementów kontrolera (edytuj, usuwaj) wewnątrz obszaru, bo to rozwiązanie też je blokuje ..
Zoltán Süle
28

Oto bardziej eleganckie rozwiązanie oparte na tym, co przedstawiono poniżej. odpowiada za zdarzenie kumulujące się z więcej niż jednego poziomu dzieci. Uwzględnia również problemy z różnymi przeglądarkami.

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
źródło
Sam Elie, wydaje się, że to nie działa w IE, Safari lub Opera. Czy masz wersję tego skryptu dla różnych przeglądarek? Działa jak urok w Firefoksie i Chrome. Dzięki.
1
Kiedy wklejam powyższą funkcję do mojego skryptu, Dreamweaver i FF wyświetlają błąd w pierwszym wierszu, „function onMouseOut (this, event) {”. Wygląda na to, że nie możesz wstawiać „this” jako parametru. Wydaje mi się, że to działa, kiedy pomijam „this” w parametrach wejściowych, ale nie wiem na pewno, ponieważ „nie wiem, czego nie wiem”.
TARKUS
1
Miałem również ten sam problem, który pojawił się w Chrome w tym samym wierszu, thisz wyłączeniem linii definicji funkcji @GregoryLewis wspomniał o rozwiązaniu problemu. Również dla większej obsługi wielu przeglądarek spróbuj tego: if (window.addEventListener) {document.getElementById ('parent'). AddEventListener ('mouseout', onMouseOut, true); } else if (window.attachEvent) {document.getElementById ('rodzic'). attachEvent ("onmouseout", onMouseOut); }
DWils
Bardzo przystojny! Najlepsza odpowiedź, jeśli wymagana jest obsługa starszych przeglądarek.
BurninLeo
13

Podziękowania dla Amjada Masada, który mnie zainspirował.

Mam następujące rozwiązanie, które wydaje się działać w IE9, FF i Chrome, a kod jest dość krótki (bez złożonego zamknięcia i poprzecznych elementów potomnych):

    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
    }
Lawrence Mok
źródło
13

zamiast onmouseout użyj onmouseleave.

Nie pokazałeś nam swojego konkretnego kodu, więc nie mogę ci pokazać na twoim konkretnym przykładzie, jak to zrobić.

Ale to jest bardzo proste: po prostu zamień onmouseout na onmouseleave.

To wszystko :) Więc proste :)

Jeśli nie wiesz, jak to zrobić, zobacz wyjaśnienie:

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

Tortowy spokój :) Ciesz się :)

kavi
źródło
To świetny okrzyk, warto sprawdzić, czy masz podobną sytuację
Chris,
12

Jeśli używasz jQuery, możesz również użyć funkcji „mouseleave”, która zajmuje się tym wszystkim za Ciebie.

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

Funkcja do_something uruchomi się, gdy mysz wejdzie dodargetdiv lub któregokolwiek z jej elementów podrzędnych, funkcja do_something_else uruchomi się tylko wtedy, gdy mysz opuści docelową dystrybucję i którekolwiek z jej elementów podrzędnych.

Jamie Brown
źródło
8

Myślę, że Quirksmode ma wszystkie potrzebne odpowiedzi (zachowanie bulgotania w różnych przeglądarkach i zdarzenia mouseenter / mouseleave ), ale myślę, że najczęstszym wnioskiem do tego bulgoczącego bałaganu jest użycie frameworka takiego jak JQuery lub Mootools (który ma centrum myszy i zdarzenia mouseleave , które są dokładnie tym, czego się spodziewałeś).

Zobacz, jak oni to robią, jeśli chcesz, zrób to sam
lub możesz stworzyć własną wersję Mootools typu „lean mean” tylko z częścią zdarzenia (i jej zależnościami).

Ruben
źródło
7

Próbować mouseleave()

Przykład:

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

   </div>
</div>

;)

Jean-Philippe
źródło
5

Znalazłem bardzo proste rozwiązanie,

po prostu użyj zdarzenia onmouseleave = "myfunc ()" niż zdarzenia onmousout = "myfunc ()"

W moim kodzie zadziałało !!

Przykład:

<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>

Ten sam przykład z funkcją 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>

Mam nadzieję, że to pomoże :)

Arminius
źródło
2

Istnieją dwa sposoby radzenia sobie z tym.

1) Sprawdź wynik event.target w swoim wywołaniu zwrotnym, aby zobaczyć, czy pasuje do Twojego nadrzędnego div

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) Lub użyj przechwytywania zdarzeń i wywołaj event.stopPropagation w funkcji wywołania zwrotnego

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>
selbie
źródło
Jak sprawić, by to działało dla onmouseout zamiast onmouseover?
John
Ups. Mój błąd. Po prostu zmieniam wszystkie odniesienia „mouseover” na „mouseout” w powyższym kodzie. To powinno wystarczyć.
selbie
W przypadku pierwszej metody, event.target zawsze pasuje do elementu nadrzędnego, gdy mysz przechodzi nad elementem dzieci, więc to nie działa
Jan
To samo dotyczy drugiej metody. Kiedy mysz wchodzi element dziecko wciąż wywołuje zdarzenie
Jan
1

Dzięki temu działa jak urok:

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

Ach, a MyDivtag wygląda tak:

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

W ten sposób, gdy mysz trafia do dziecka, wnuka itp. ... style.display='none'nie zostaje wykonana; ale kiedy onmouseout wychodzi z MyDiv, działa.

Nie ma więc potrzeby zatrzymywania propagacji, używania timerów itp.

Dzięki za przykłady, mógłbym zrobić z nich ten kod.

Mam nadzieję, że to komuś pomoże.

Można również ulepszyć w ten sposób:

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

A potem znaczniki DIV, takie jak to:

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

Więc bardziej ogólnie, nie tylko dla jednego div i nie ma potrzeby dodawania id="..."na każdej warstwie.

z666zz666z
źródło
1

Jeśli masz dostęp do elementu, do którego zdarzenie jest dołączone wewnątrz mouseoutmetody, możesz użyć, contains()aby sprawdzić, czy event.relatedTargetjest to element podrzędny, czy nie.

Podobnie event.relatedTargetjak element, do którego przeszła mysz, jeśli nie jest to element podrzędny, zostałeś usunięty z elementu.

div.onmouseout = function (event) {
    if (!div.contains(event.relatedTarget)) {
        // moused out of div
    }
}
astronauta
źródło
1

Na Angular 5, 6 i 7

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

Potem dalej

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');
 }
}
Alan Torrico
źródło
0

Sprawdzam przesunięcie oryginalnego elementu, aby uzyskać współrzędne strony granic elementu, a następnie upewniam się, że akcja przesuwania myszy jest wyzwalana tylko wtedy, gdy wskaźnik myszy znajduje się poza tymi granicami. Brudne, ale działa.

$(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
}
}
saranicole
źródło
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
});
Michael Vaganov
źródło
Proszę o wyjaśnienie swojej odpowiedzi.
Sebass van Boxel
0

Jeśli dodałeś (lub masz) klasę CSS lub identyfikator do elementu nadrzędnego, możesz zrobić coś takiego:

<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
  }
}

Tak więc rzeczy są wykonywane tylko wtedy, gdy zdarzenie jest w nadrzędnym div.

Karthik Palaniappan
źródło
0

Chciałem tylko się z tobą czymś podzielić.
Trudno mi było z wydarzeniami ng-mouseenteri ng-mouseleavewydarzeniami.

Studium przypadku:

Stworzyłem pływające menu nawigacyjne, które jest przełączane, gdy kursor znajduje się nad ikoną.
To menu znajdowało się na górze każdej strony.

  • Aby obsłużyć wyświetlanie / ukrywanie w menu, przełączam klasę.
    ng-class="{down: vm.isHover}"
  • Aby przełączyć vm.isHover , używam zdarzeń myszy ng.
    ng-mouseenter="vm.isHover = true"
    ng-mouseleave="vm.isHover = false"

Na razie wszystko było w porządku i działało zgodnie z oczekiwaniami.
Rozwiązanie jest czyste i proste.

Nadchodzący problem:

W konkretnym widoku mam listę elementów.
Dodałem panel akcji, gdy kursor znajduje się nad elementem listy.
Użyłem tego samego kodu co powyżej do obsługi zachowania.

Problem:

Zorientowałem się, że kiedy mój kursor znajduje się w pływającym menu nawigacyjnym, a także na górze elementu, występuje między sobą konflikt.
Pojawił się panel akcji, a pływająca nawigacja została ukryta.

Chodzi o to, że nawet jeśli kursor znajduje się nad pływającym menu nawigacyjnym, uruchamiany jest element listy ng-mouseenter.
Nie ma to dla mnie sensu, bo spodziewałbym się automatycznej przerwy w propagacji myszy.
Muszę powiedzieć, że byłem rozczarowany i spędziłem trochę czasu, aby dowiedzieć się tego problemu.

Pierwsze myśli:

Próbowałem użyć tych:

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

Połączyłem wiele zdarzeń wskaźnikowych (mousemove, mouveover, ...), ale żadne mi nie pomogło.

Rozwiązanie CSS:

Znalazłem rozwiązanie z prostą właściwością css, której używam coraz częściej:

pointer-events: none;

Zasadniczo używam tego w ten sposób (na mojej liście elementów):

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

W tym trudnym przypadku zdarzenia ng-mouse nie będą już wyzwalane, a moje pływające menu nawigacyjne nie będzie się już zamykać, gdy kursor znajdzie się nad nim i nad elementem z listy.

Iść dalej:

Jak można się spodziewać, to rozwiązanie działa, ale mi się nie podoba.
Nie kontrolujemy naszych wydarzeń i jest źle.
Ponadto musisz mieć dostęp do vm.isHoverlunety, aby to osiągnąć i może to być niemożliwe lub niemożliwe, ale w jakiś sposób brudne.
Mógłbym zrobić skrzypce, gdyby ktoś chciał spojrzeć.

Nie mam jednak innego rozwiązania ...
To długa historia i nie mogę ci dać ziemniaka, więc wybacz mi przyjacielu.
W każdym razie pointer-events: nonejest życie, więc pamiętaj o tym.

C0ZEN
źródło