Zu viele onMouseouts!
Rasmus
- javascript
0 Felix Riesterer
Hallo Leute,
Ich bin etwas konfusioniert über mein Problem. Das Problem, das ich jetzt vorstelle, habe ich schon etwas vereinfacht - im Kern sollte es das aber sein:
Ich erstelle mit PHP ganz dynamisch eine beliebig große Tabelle. Die Zeilen "<tr>" haben dabei alle eine eindeutige id zugewiesen bekommen und sollen aufleuchten, wenn die Maus des Betrachters über die Zeile fährt. In etwa so:
<tr id="tablerow_1" onMouseover="highlight(this.id)" onMouseout="darken(this.id">
Soweit ist noch alles dufte. Jetzt soll das Highlighting der Tabellenzeilen nicht etwa sofort und abrupt sondern ganz modern smooth passieren. Das Ganze natürlich in Javascript:
function highlight(id)
{
if (window.document.all[id].style.backgroundColor != '#a0a0a0')
{
window.setTimeout("forcecolor('"+id+"', '#909090')", 0);
window.setTimeout("forcecolor('"+id+"', '#959595')", 40);
window.setTimeout("forcecolor('"+id+"', '#a0a0a0')", 80);
}
}
function darken(id)
{
if (window.document.all[id].style.backgroundColor != '#888888')
{
window.setTimeout("forcecolor('"+id+"', '#959595')", 0);
window.setTimeout("forcecolor('"+id+"', '#909090')", 40);
window.setTimeout("forcecolor('"+id+"', '#888888')", 80);
}
}
Die if-Abfrage ist eigentlich nutzlos, aber ich habe sie schon eingebaut, um mein Problem zu beheben, was trotzdem nicht geklappt.
Was ist jetzt mein Problem?
Wenn in der Tabelle andere Elemente auftauchen wie ein Bild oder ein kursiver Text oder so, dann passiert Folgendes: Fährt die Maus innerhalb der Zeile auf ein Bild oder einen kursiven Text, wird einmal darken(this.id) und sofort danach highlight(this.id) aufgerufen, so als ob die Maus die Zeile verlassen hätte und sofort wieder überfahren würde. Ziemlicher Quatsch, weil die Maus die Zeile nie verlässt!
Das Verhalten beobachte ich bei allen Browsern (Safari, Firefox, InternetExplorer, Opera), wobei Opera noch einen Tick anders ist: bei Opera kann ich die Maus so langsam auf das Bild bewegen, dass es einen Punkt (oder eine Umrandungslinie) gibt, wo nur der Befehl darken(this.id), nicht aber highlight(this.id) raus geht. Erst wenn ich die Maus bewege (Richtung egal) wird highlight(this.id) wieder aufgerufen.
In meinen Augen kann das Verhalten der Browser kaum gewollt sein. Irritieren tut mich aber trotzdem, dass das Verhalten bei allen Browsern zu finden ist. Wenn ich die Timeouts weglasse und sofort färbe, entsteht der Fehler nicht.
Versucht habe ich schon, wie man oben sieht, die derzeitige Farbe abzufragen, den ersten Timeout auf eine oder mehr Millisekunden zu setzen. Einen Handler wie document.all.id.ismouseover scheint es in Javascript nicht zu geben.
Meine Frage ist jetzt, ob es einen Ausweg für mich gibt, ob das ein bekannter Fehler ist oder ob ich mein letztes Gebet sprechen kann. Echte Hoffnung habe ich nicht mehr, aber euch traue ich doch noch eine Menge zu.
Lieber Rasmus,
Du hast ein Problem mit dem Bubbling des Events. Das Überfahren eines Bildelements findet aus der Perspektive des DOM-Baumes natürlich ebenso innerhalb des <tr>-Elements statt, da das Bildelement ein Nachfahrenelement dieses <tr>-Elements ist. Du kannst Dir vorstellen, wie eine Verschachtelung der Elemente eine ebenso verschachteltes Auslösen der Events erzeugt...
Mein Lösungsvorschlag ist der: Benutze das http://de.selfhtml.org/javascript/sprache/eventhandler.htm#onmousemove@title=onmousemove-Event _dokumentweit_. Prüfe innerhalb dieses Events, in welchem Element wir uns gerade befinden (für IE event.srcElement, für andere event.target), und ob dieses ein Nachfahrenelement einer Zeile ist.
Du erstellst dann für jede Tabellenzeile zwei Dinge:
1.) eine Eigenschaft, in der steht, ob aufgehellt, oder abgedunkelt werden soll (geht mit true/false)
2.) eine Funktion, die den Vorgang des Aufhellens oder Abdunkelns jeweils einen Schritt weiterführt
Du erstellst für die Seite eine Art Zeitgeber (setIntervall), der der Reihe nach jede Tabellenzelle abklappert, um dort die jeweilige Funktion aufzurufen, damit dort eventuell anstehende Änderungen weitergeführt werden können.
Es ist sinnvoll, Dir dazu ein Rahmenobjekt zu erstellen (siehe Artikel zu Fader-Framework), in dem sowohl dieser Timer, als auch die Referenzen zu den Tabellenzeilen (und damit deren Animationsfunktionen) gespeichert werden.
Liebe Grüße,
Felix Riesterer.
Hi Felix,
Vielen, vielen Dank für diese Antwort! Du hast mir in der Tat weiter geholfen. Einerseits war "Event Bubbling" wohl ein Begriff, der mir gefehlt hat. Andererseits hat mir Dein Lösungsansatz auch sehr geholfen.
Weil bei mir die Tabellen dynamisch mit PHP erstellt werden und im Extremfall sogar mit AJAX vom Server geholt werden, habe ich mich jedoch dagegen entschieden, in PHP für jede Tabellzeile eine Variable anzulegen. Stattdessen initiiere ich in Javascript ein globales Array mit allen "bekannten" Zeilen (beim Aufruf der Seite ist dies ein leeres Array). Falls jetzt die Maus auf eine unbekannte Zeile zieht, meldet sich diese Zeile im Array an und kann von da an mit Javascript gefärbt werden, wie Du, Felix, es auch vorgeschlagen hast. Das Ganze geht solange gut, bis eine AJAX-Funktion auf die Idee kommt, Tabellenzeilen wieder zu löschen. Dann müsste die AJAX-Funktion die Zeilen auch im Array wieder abmelden. Aber welch ein Glück, dass ich das Problem nicht habe habe!
Als Dankeschön und für alle Leute, die wie ich tagelang das ganze Netz nach einer Lösung durchforsten, poste ich noch meine Testseite, die ich vorhin programmiert habe:
<html>
<head>
<title>Tabellen-Highlight-Test</title>
<script language="JavaScript">
<!--
function forcecolor(id, color)
{
window.document.all[id].style.backgroundColor = color;
}
function highlight(id)
{
window.setTimeout("forcecolor('"+id+"', '#aaaaaa')", 0);
window.setTimeout("forcecolor('"+id+"', '#bbbbbb')", 60);
window.setTimeout("forcecolor('"+id+"', '#cccccc')", 120);
}
function darken(id)
{
window.setTimeout("forcecolor('"+id+"', '#cccccc')", 0);
window.setTimeout("forcecolor('"+id+"', '#bbbbbb')", 60);
window.setTimeout("forcecolor('"+id+"', '#aaaaaa')", 120);
}
var alltherows = new Array();
var isrowlightened = new Array();
function canyouhandlethis(e)
{
var i;
var welchezeile = -1;
// -> Zuerst schauen wir, ob das event.target Teil einer Tabellenzeile ist
var element = e.target;
while ((element != "[object HTMLTableRowElement]") && (element != null))
element = element.parentNode;
// -> Wenn ja, schauen wir in alltherows nach, ob wir die Zeile schon kennen.
if (element == "[object HTMLTableRowElement]")
{
for (i=0; i < alltherows.length; i++)
if (alltherows[i] == element.id)
{
welchezeile = i;
break;
}
// -> Wenn nein, wird sie zu alltherows hinzugefuegt: alltherows.push();
if (welchezeile == -1)
{
alltherows.push(element.id);
// -> Das hilft aber nur, wenn wir auch isrowlightened.push(true) gesetzt wird.
isrowlightened.push(true);
// Und jetzt muss das event.target folgerichtig auch sein highlighting bekommen.
highlight(element.id);
welchezeile = alltherows.length-1;
} else {
// -> Falls die Zeile schon bekannt ist UND noch nicht highlighted, wird sie
// angeknipst und isrowlightened[i] auf true gesetzt.
if (isrowlightened[i] == false)
{
isrowlightened[i] = true;
highlight(element.id);
}
}
}
// -> Und nun werden alle ANDEREN Zeilen in alltherows dunkel gemacht, wenn
// das parallele isrowlightened[i] true gesetzt ist. Setze es dafuer auch auf false.
for (i=0; i < alltherows.length; i++)
if ((i != welchezeile) && (isrowlightened[i] == true))
{
isrowlightened[i] = false;
darken(alltherows[i]);
}
}
//-->
</script>
</head>
<body onmousemove="canyouhandlethis(event)" bgcolor="#aaaaaa">
Hier noch ein ganz normaler Testtext, der nur im Body-Tag hängt.
<br>
<br>
<br>
<table>
<tr class="lightrow" id="zeile_1"><td>Hier <i>kann</i> was abgehen. Aber was?</td><td>Das was wohl keiner.</td></tr>
<tr class="lightrow" id="zeile_2"><td>Hier <i>kann</i> was abgehen. Aber was?</td><td>Das was wohl keiner.</td></tr>
<tr class="lightrow" id="zeile_3"><td>Hier <i>kann</i> was abgehen. Aber was?</td><td>Das was wohl keiner.</td></tr>
<tr class="lightrow" id="zeile_4"><td>Hier <i>kann</i> was abgehen. Aber was?</td><td>Das was wohl keiner.</td></tr>
<tr class="lightrow" id="zeile_5"><td>Hier <i>kann</i> was abgehen. Aber was?</td><td>Das was wohl keiner.</td></tr>
</table>
</body>
</html>
Dies muss natürlich noch für IE und andere Browser kompatibel gemacht werden (habe es nur mit Firefox getestet).
Ich finde die Lösung recht elegant, weil man nur eine Javascript-Funktion braucht, die alles selbst regelt. Der noch einzufügende PHP-Code zum Erstellen der Tabellen bleibt also minimal. Problematisch könnte sein, dass Tabellen in Tabellen noch nicht berücksichtigt worden sind (nur die niederste Zeile wird beleuchtet) und dass es nicht ad hoc möglich ist, nur bestimmte Klassen von Zeilen zu highlighten; parentNode findet keine Zeilenklassen sondern nur generell Zeilen. Da müsste man auch noch etwas tricksen. Aber das Grundgerüst dürfte klar sein.
Viele Grüße
Rasmus
window.document.all[id].style.backgroundColor = color;
Verwende document.getElementById statt document.all.
document.all ist eine IE-5-spezifische Technik, die andere Browser nur aus Kompatibilitätsgründen unterstützen.
Firefox sollte dir auch eine entsprechende Warnung in der JavaScript-Konsole ausgeben.
}
function highlight(id)
{
window.setTimeout("forcecolor('"+id+"', '#aaaaaa')", 0);
Das kannst du so schreiben (anonyme Funktion übergeben):
window.setTimeout(function () { forcecolor(id, '#aaaaaa'); }, 123);
Und musst dir um das Zusammenbauen eines Strings keine Gedanken machen
Den ersten Aufruf musst du nicht verzögern (setTimeout mit 0 macht hier keinen Sinn).
var element = e.target;
Browserübergreifend:
var element = e.target || e.srcElement;
http://redaktion.selfhtml.org/selfhtml-preview/javascript/einbindung.html#currenttarget-target
while ((element != "[object HTMLTableRowElement]") && (element != null))
Du gehst hier davon aus, dass der Browser das Elementobjekt genau auf diese Weise zu einem String umwandelt. Das ist aber nicht der Fall, also prüfe
element.nodeName.toLowerCase() == 'tr'
Das Element kann nicht null sein, jedes Ereignis hat ein Zielelement. Wozu die Abfrage?
if (element == "[object HTMLTableRowElement]")
Siehe oben.
Mathias
Hallo molily!
document.all ist eine IE-5-spezifische Technik
Kannte das nicht schon IE4?
Viele Grüße aus Frankfurt/Main,
Patrick
Hi Mathias,
Stimmt, ich habe den Code etwas arg zusammengetreten und dirty gemacht. Dafür mal ein dickes Entschuldigung.
Den ersten Aufruf musst du nicht verzögern (setTimeout mit 0 macht hier keinen Sinn).
Sehe ich ein. Ich war wohl etwas sehr faul bzw. rigoros mit Copy-Paste.
»» var element = e.target;
Browserübergreifend:
var element = e.target || e.srcElement;
http://redaktion.selfhtml.org/selfhtml-preview/javascript/einbindung.html#currenttarget-target
Danke, stimmt. Sehr elegant.
»» while ((element != "[object HTMLTableRowElement]") && (element != null))
Du gehst hier davon aus, dass der Browser das Elementobjekt genau auf diese Weise zu einem String umwandelt. Das ist aber nicht der Fall, also prüfe
element.nodeName.toLowerCase() == 'tr'
Sowas dachte ich mir schon, habe aber keine Spezifikation auf die Schnelle gefunden (oder nicht intensiv genug gesucht) und dann einfach auf die Stringausgabe des Firefox überprüft, was zu meiner eigenen Überraschung auch geklappt hat. Danke für die saubere Variante.
Das Element kann nicht null sein, jedes Ereignis hat ein Zielelement. Wozu die Abfrage?
Aber element.parentNode kann null sein. Und damit meine while-Schleife sich nicht im Kreis dreht, habe ich die Abfrage eingebaut. Ich denke, dieser Punkt ist schon richtig so.
Viele Grüße
Rasmus
Lieber Rasmus,
freut mich, dass Du mit meinen Ideen soviel weitergekommen bist!
Aber element.parentNode kann null sein.
Das ist Unsinn. Wenn ein Element existiert, das nicht das <html>-Element des DOM-Baumes ist, dann hat es ein übergeordnetes Element als Vorfahrenelement. Sonst ist es ein Element, das nicht im DOM-Baum verankert ist, und das erhälst Du mit e.target || e.srcElement
nunmal nicht!
Und damit meine while-Schleife sich nicht im Kreis dreht
... solltest Du prüfen, ob Du schon das <body>-Element erreicht hast.
while (!element.tagName || !element.tagName.match(/^(tr|body)$/i))
element = element.parentNode;
Liebe Grüße,
Felix Riesterer.