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