molily: onMouseover bei verschachtelten Elementen

Beitrag lesen

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