molily: Die kunterbunte Welt des Event-Handlings

Beitrag lesen

Hallo,

Willkommen in der kunterbunten Welt des Event-Handlings!

Mouseover- und Mouseout-Events im IE haben die Eigenschaft, dass sie nicht einfach bei dem Element passieren, bei dem du den Handler registriert, sondern dass sie anschließend »aufsteigen« (Bubbling).

Gehen wir mal von folgender Beispielstruktur aus:

<ul id="ul">
  <li id="ul-li">
    <a id="ul-li-a" href="#">link</a>
    <ul id="ul-li-ul">
      <li id="ul-li-ul-li-1">
        <a id="ul-li-ul-li-1-a" href="#">link</a>
      </li>
      <li id="ul-li-ul-li-2">
        <a id="ul-li-ul-li-2-a" href="#">link</a>
      </li>
      (...)
    </ul>
  </li>
  (...)
</ul>

Du hast hier also eine Handvoll Elementknoten, die bei der grafischen Darstellung allesamt Boxen erzeugen.

Wechselt der Mauszeiger nun von einer Box zu einer anderen, so findet immer ein Mouseout- und ein Mouseover-Event statt. Das gilt auch für Unterelemente, deren Boxen eigentlich in der Box des Elternelements enthalten sind.

Dies erst einmal an einem einfacheren Beispiel verdeutlicht:

<div onmouseover="l('mouseover außen')" onmouseout="l('mouseout außen')" style="background:#999;">
  <div>bla</div>
  <div onmouseover="l('mouseover innen')" onmouseout="l('mouseout innen')" style="background:#666;">bla</div>
  <div>bla</div>
</div>

l() ist eine Funktion, die den übergebenen Text in eine Textarea schreibt, sodass man dort ein Log aller Events hat.

Wir nehmen also an:
1. Wenn wir den Mauszeiger über die Fläche des äußeren divs bewegen, kommt »mouseover außen«.
2. Wenn wir den Mauszeiger über die Fläche des inneren divs bewegen, kommt »mouseout außen« und »mouseover innen«.

Soweit logisch, das musst du aber erst einmal beachten. Du klappst das Menü auf, wenn ein Mouseover-Event beim li-Element passiert. Und klappst es zu, wenn dort ein Mouseout-Event passiert (im obigen Beispiel: ID ul-li).

Das Problem: Wenn sich der Mauszeiger dann zu einem Link im aufgeklappten Untermenü bewegt (ID ul-li-ul-li-1-a), passiert beim Element mit der ID ul-li ein Mouseout-Event! Sprich, das Menü würde zuklappen, sobald man die Maus im Untermenü bewegt.

Soweit die vereinfachte Theorie, die Praxis ist komplizierter, denn nun kommen wir … Trommelwirbel … zum Bubbling.

Schauen wir uns noch einmal das Beispiel mit den divs an:
Welche Events werden gefeuert, wenn man die Maus von der Fläche des äußeren divs auf die Fläche des inneren divs bewegt?

1. mouseout außen
2. mouseover innen
   Soweit klar, jetzt kommts:
3. mouseover außen

Huch?! Wieso findet dieser Mouseover-Event statt, wo der Mauszeiger doch gerade die Fläche des äußeren divs verlassen hat?

Zur Verdeutlichung noch einmal andersherum:
Welche Events werden gefeuert, wenn man den Mauszeiger von der Fläche des inneren divs auf die Fläche des äußeren divs bewegt?

1. mouseout innen
   Soweit klar, jetzt kommts:
2. mouseout außen
   Huch?! Was macht dieser Event? Wir haben die Fläche des äußeren divs nicht verlassen, sondern Bewegen den Mauszeiger gerade in diese Fläche...
3. mouseover außen
   Das ist wieder verständlich.

Nun, wie ist dieser Effekt zu erklären? Er ist dadurch zu erklären, dass sowohl Mouseover- als auch Mouseout-Events in der Elementstruktur hochsteigen (bubblen).

Sprich, wenn beim inneren div ein Mouseover passiert, wird dort der entsprechende Event-Handler ausgeführt. Dann steigt der Event hoch zum äußeren div (und noch weiter im Elementenbaum bis zum document-Knoten, das interessiert hier jetzt nicht). Dort wird *auch* der Mouseover-Handler ausgeführt. Das sieht man in der ersten Liste.

Dasselbe bei der zweiten Liste: Beim inneren div passiert ein Mouseout-Event, der dann hochsteigt und auch den Mouseout-Handler beim äußeren div auslöst.

Wenn man also den Mauszeiger in einem Untermenü bewegt, werden ständig irgendwelche Mouseover- und Mouseout-Events erzeugt. Zum einen werden mouseout-Events erzeugt, wenn man sich von der Fläche von ID ul-li auf die Fläche irgendeines Unterelements bewegt.

Jetzt kommt das Bubbling ins Spiel: Alle Mouseover- und Mouseout-Events, die bei den Unterelementen passieren, steigen auf und erreichen schließlich das Element mit ID ul-li.

Bei diesem Element kommen also ständig irgendwelche Mouseover- und Mouseout-Events an, die völlig verwirren: Sie haben nämlich nichts damit zu tun, dass sich der Mauszeiger in die Fläche der Kindelemente eintritt bzw. sie verlässt. Man bleibt ja mit der Maus im Untermenü.

Diese Events, die hochsteigen, lösen also ständig unkontrolliert einblenden() und ausblenden() aus. Dies musst du verhindern.

Die Strategie ist: Man muss alle Mouseover- und Mouseout-Events ignorieren, die dabei passieren, dass sich die Maus zwischen den Kindelementen hin und her. Aufsteigende Mouseover-Events kann man einfach ignorieren: Wenn das Untermenü bereits aufgeklappt ist, wird es nicht noch einmal eingeblendet. Bei Mouseout-Events muss man abfragen, von wo nach wo sich die Maus bewegt.

In diesem Posting habe ich ein ähnliches Problem beispielhaft gelöst. Es wird abgefragt, von wo die Maus kommt bzw. wohin sie geht. Es geht aber noch einfacher, dazu dieses Beispiel:
http://molily.de/temp/menu.html

Überflüssige, aufsteigende Mouseover-Events werden abgefangen, indem es eine globale Variable gibt, in der der Ausklapp-Status des Untermenüs gespeichert wird.

Überflüssige Mouseout-Events werden dadurch abgefangen, indem abgefragt wird, ob das Element, zu dem sich der Mauszeiger gerade bewegt, innerhalb des li-Elements liegt. Dazu bietet der Internet Explorer die Methode Element1.contains(Element2) an, die zurückgibt, ob sich Element2 innerhalb von Element1 befindet. Das Element, auf dessen Fläche der Mauszeiger infolge des Mouseouts wechselt, ist in window.event.toElement gespeichert. Erst wenn sich der Mauszeiger auf die Fläche eines Elements bewegt, welches außerhalb liegt, soll das Menü zuklappen.

Disclaimer: Da es hier um in Script nur für den IE geht, beziehe ich mich hier ausschließlich auf Microsoft-eigene Techniken, die in anderen Browsern nicht oder anders funktionieren. Die Aufgabe müsste man dafür für andere Browser, die im Gegensatz zum IE dem DOM-Events-Standard folgen, auch anders lösen.

Schalten Sie auch nächstes Mal wieder ein.

Mathias