Remo: onMouseover bei verschachtelten Elementen

Hallo,

ich denke Probleme wie mein Folgendes sind hier schon hundertfach gestellt worden, allerdings habe ich trotz Suche hier im Forum nichts passendes gefunden.

Also, ich hab etwas in der Art:

  
<div class="outer" onMouseover="show('inner')" onMouseout="hide('inner')">  
   <a class="inner" href="">  
         <img alt="" border="0" src="images/an_image.gif">  
   </a>  
</div>  

D.h. bei onMouseover über das äussere div-Element wird der Link angezeigt. Sobald ich mit der Maus über das Bild im Link fahre, wird der onMouseout-Event getriggert und hide(inner) ausgeführt. Das wäre aber nicht das Ziel der Sache! Da 'outer' das 'inner'-Element umfasst, erwarte ich, dass selbst wenn ich mit der Maus auf einem inner-Objekt stehe, hide nicht ausgeführt wird, da ich noch innerhalb des 'outer'-Elements stehe. Klar was ich meine?  ;)

Besten Dank für Hinweise,
Remo

  1. Lieber Remo,

    mit welchen Browsern hast Du Dein Script getestet? Verhalten sich alle getesteten Browser gleich?

    Wo kann man sich das mal anschauen?

    Liebe Grüße aus Ellwangen,

    Felix Riesterer.

  2. Hallo,

    D.h. bei onMouseover über das äussere div-Element wird der Link angezeigt. Sobald ich mit der Maus über das Bild im Link fahre, wird der onMouseout-Event getriggert und hide(inner) ausgeführt. Das wäre aber nicht das Ziel der Sache! Da 'outer' das 'inner'-Element umfasst, erwarte ich, dass selbst wenn ich mit der Maus auf einem inner-Objekt stehe, hide nicht ausgeführt wird, da ich noch innerhalb des 'outer'-Elements stehe. Klar was ich meine?  ;)

    Das Problem ist nicht trivial.

    Zunächst einmal: hide() wird ausgeführt, weil mouseout-Events im Elementenbaum aufsteigen (bubbling). Das heißt konkret:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">  
    <html><head><title></title>  
    <script type="text/javascript">  
    [code lang=javascript]function log (message) {  
     if (typeof log.t == "undefined")  
      log.t = document.getElementById("t");  
     log.t.value = log.t.value + message + "\n";  
    }  
    function show (event) {  
     var target = event.target || event.srcElement;  
     log("over  target: " + target.id + " > show");  
     document.getElementById("inner").style.visibility = "visible";  
    }  
    function hide (event) {  
     var target = event.target || event.srcElement;  
     log("out   target: " + target.id + " > hide");  
     document.getElementById("inner").style.visibility = "hidden";  
    }  
    
    ~~~</script>  
    <style type="text/css">  
    ~~~css
    #outer {width:200px; background-color:#fdd; padding:2em;}  
    #inner {background-color:#ddf; margin:auto; visibility:hidden;}
    

    </style>
    </head><body id="body">

    <div id="outer" onmouseover="show(event)" onmouseout="hide(event)">
       <div id="inner" href="">
             <img id="bild" alt="" border="0" src="http://src.selfhtml.org/logo.gif">
       </div>
    </div>

    <p><textarea id="t" cols="60" rows="25"></textarea></p>[/code]

    Dieses Beispiel greift auf die Eigenschaft target bzw. srcElement des Event-Objektes zu, um in Erfahrung zu bringen, welcher Elementknoten der Ursprung des Events ist. Die Funktion log() verzeichnet alle mouseover- und mouseout-Mausereignisse in der Textarea.

    Wenn man den Mauszeiger von außerhalb auf die Fläche des Elements #outer und von dort auf die Fläche des Elements #bild bewegt, passieren folgende Mausereignisse:

    over  target: outer > show
    out   target: outer > hide
    over  target: inner > show
    out   target: inner > hide
    over  target: bild > show

    Wie man sieht, steigen die mouseover- und mouseout-Ereignisse bei Elementen innerhalb von #outer im Elementenbaum auf und lösen beim Element #outer die Handler show() und hide() aus.

    Es entsteht dadurch ein überflüssiges Zeige- und Versteckspiel: Beim Übergang des Mauszeigers von der Fläche von #outer auf die Fläche von #inner wird zunächst onmouseout bei #outer gefeuert. Dadurch wird #inner versteckt. Im gleichen Moment wird aber mouseover bei #inner ausgelöst. Dieser Event steigt auf und löst den mouseover-Handler von #outer aus. Es wird also show() aufgerufen und #inner bleibt letztlich sichtbar. Dasselbe Spiel beim Übergang von #inner auf #bild.

    Nun, dieses Spiel ist zwar überflüssig, es führt aber dazu, dass das Einblenden und Ausblenden von #inner letztlich durchaus funktioniert. Was ist also das Problem?

    Dieses überflüssige Spiel kann man natürlich verhindern. Zuerst einmal sollten die mouseover- und mouseout-Handler von #outer nur Events verarbeiten, deren Ursprung #outer selbst ist. Damit kann man die meisten Ereignisse in obiger Liste ignorieren.

    Es bleibt aber »out   target: outer > hide« beim Übergang des Mauszeigers von #outer nach #inner. Woher weiß man, dass sich der Mauszeiger im Grunde gar nicht aus der #outer-Box herausbewegt? Der Lösungsansatz ist: Man fragt in der hide()-Funktion ab, welchem Element die Fläche gehört, auf das sich der Mauszeiger mit dem Verlassen der Elementfläche bewegt. Dabei helfen die Eigenschaften relatedTarget bzw. toElement des Event-Objekts weiter. Ob das Ursprungselement in #outer drinsteckt oder nicht, lässt sich mit contains() in Erfahrung bringen. Dies ist aber eine Microsoft-Erfindung ist, die zumindest Gecko nicht kennt. Man kann aber auch einfach die ID, Klasse oder den parentNode abfragen.

    Beim Übergang des Mauszeigers von #bild auf die fläche außerhalb von #outer passieren folgende Ereignisse:

    out   target: bild > hide
    over  target: inner > show
    out   target: inner > hide
    over  target: outer > show
    out   target: outer > hide

    Hierbei filtert die Begrenzung auf #outer als Ursprungselement alle Ereignisse bis auf »over  target: outer > show«. Dieses kann man analog mit relatedTarget bzw. fromElement filtern. Darin wird bei einem mouseover-Ereignis gespeichert, welchem Element die Fläche gehört, von der der Mauszeiger kommt. Wenn dieses Element #inner ist, muss show() das Ereignis ignorieren.

    Alles zusammengewürfelt sieht führt uns zu folgenden Funktionen aus:

    function show (event) {  
     var target = event.target || event.srcElement || false;  
     var fromElement = event.relatedTarget || event.fromElement || false;  
      
     if (target.id == "outer" && fromElement.id != "inner") {  
      log("over  target: " + target.id + "  from: " + fromElement.id + "   > show");  
      document.getElementById("inner").style.visibility = "visible";  
     } else {  
      log("over  target: " + target.id + "  from: " + fromElement.id + "  > ignore");  
     }  
    }  
    function hide (event) {  
     var target = event.target || event.srcElement || false;  
     var toElement = event.relatedTarget || event.toElement || false;  
      
     if (target.id == "outer"  && toElement.id != "inner") {  
      log("out   target: " + target.id + "  to:   " + toElement.id + "   > hide");  
      document.getElementById("inner").style.visibility = "hidden";  
     } else {  
      log("out   target: " + target.id + "  to:   " + toElement.id + "  > ignore");  
     }  
    }
    

    Damit müssen alle unwichtigen Ereignisse ignoriert werden. Beispiel des Bewegens von Außen zum Bild und wieder zurück:

    over  target: outer  from: body   > show
    out   target: outer  to:   inner  > ignore
    over  target: inner  from: outer  > ignore
    out   target: inner  to:   bild  > ignore
    over  target: bild  from: inner  > ignore

    out   target: bild  to:   inner  > ignore
    over  target: inner  from: bild  > ignore
    out   target: inner  to:   outer  > ignore
    over  target: outer  from: inner  > ignore
    out   target: outer  to:   body   > hide

    Grüße,
    Mathias

    1. Mathias,

      super Antwort, danke dass Du Dir Zeit dafür genommen hast!

      Remo

  3. Hi Remo,

    ich denke Probleme wie mein Folgendes sind hier schon hundertfach gestellt worden, allerdings habe ich trotz Suche hier im Forum nichts passendes gefunden.

    Weil du nach JavaScript gesucht hast, und das ein CSS-Problem ist.

    D.h. bei onMouseover über das äussere div-Element wird der Link angezeigt. Sobald ich mit der Maus über das Bild im Link fahre, wird der onMouseout-Event getriggert und hide(inner) ausgeführt. Das wäre aber nicht das Ziel der Sache! Da 'outer' das 'inner'-Element umfasst, erwarte ich, dass selbst wenn ich mit der Maus auf einem inner-Objekt stehe, hide nicht ausgeführt wird, da ich noch innerhalb des 'outer'-Elements stehe. Klar was ich meine?  ;)

    Ja. Das kann man wunderbar mit CSS lösen:
    inner in outer: nicht anzeigen
    inner in mausdrauf-outer: anzeigen

    http://de.selfhtml.org/css/formate/zentrale.htm#verschachtelte_elemente
    http://de.selfhtml.org/css/formate/zentrale.htm#pseudoformate (:hover)

    Gruß, Marian