Kapitel 9 Images
Es gibt Maler, die die Sonne in einen gelben Fleck verwandeln. Es gibt aber andere, die dank ihrer Kunst und Intelligenz einen gelben Fleck in die Sonne verwandeln.
– Pablo Picasso
Einer der beliebtesten JavaScript-Effekte im World Wide Web – und wahrscheinlich der Effekt, der am häufigsten falsch eingesetzt wird. In diesem Kapitel erfahren Sie, wie es richtig funktioniert.
Das Image-Objekt tauchte zum ersten Mal in einer Beta-Version des Netscape Navigator 3 auf. Damit war es erstmals möglich, mit JavaScript einzelne Grafiken eines HTML-Dokuments anzusprechen. Besonders pfiffig – und praktisch – war die Möglichkeit, Grafiken auszutauschen, was Animationen und andere grafische Effekte ermöglicht.
Zu ungefähr derselben Zeit wurde die Version 3 des Microsoft Internet Explorer veröffentlicht. Da sich das Konkurrenzprodukt aus dem Hause Netscape noch in der Beta-Testphase befand, wurde das Image-Objekt nicht mehr in den JavaScript-Funktionsumfang des Internet Explorer übernommen. Dies ist übrigens die Hauptursache für unsaubere Umsetzungen im World Wide Web. Die Macintosh-Variante des »IE3« kam etwas später als das Windows-Pendant heraus, und da sich das Image-Objekt bei Webdesignern großer Beliebtheit erfreute, wurde eine rudimentäre Unterstützung für den flotten Bildertausch in den Browser integriert. Ab Version 4 unterstützt der Internet Explorer übrigens auf allen Plattformen das Image-Objekt, und zwar vollständig.
9.1 Bildlein-Wechsle-Dich  
Doch was war es, das den Webdesignern so gut am Image-Objekt gefiel? JavaScript wurde bis dato hauptsächlich für Laufschriften und für Formulare benutzt. Hier war es zum erstenmal auch möglich, mit JavaScript grafische Effekte zu schaffen. Eine der Hauptanwendungen wird lapidar mit »Mouseovers« bezeichnet. Fährt die Maus über eine Grafik, so ändert diese ihre Gestalt; befindet sich die Maus dann nicht mehr über der Grafik, nimmt diese wieder ihre ursprüngliche Gestalt an.
Zwar ist es nicht möglich, eine Grafik zu manipulieren (etwa die Farbpalette zu verändern o. ä.), aber man kann die Grafik einfach austauschen. In Abbildung 9.1 sehen Sie zwei Grafiken, galileo_over.gif und galileo_ out.gif. Unser Ziel ist es nun, die Grafik galileo_out.gif anzuzeigen. Nur wenn sich der Mauszeiger über der Grafik befindet, soll die Grafik galileo_over.gif angezeigt werden.
Abbildung 9.1 Die beiden Grafiken für den Mouseover-Effekt
|
Der folgende Code erfüllt die Anforderungen; eine Erläuterung folgt anschließend.
<HTML>
<HEAD>
<TITLE>Bildlein-Wechsle-Dich</TITLE>
<SCRIPT LANGUAGE="JavaScript"><!--
function over(){
if (document.images)
document.grafik.src = "galileo_over.gif"
}
function out(){
if (document.images)
document.grafik.src = "galileo_out.gif"
}
//--></SCRIPT>
</HEAD>
<BODY>
<A HREF="http://www.galileo-press.de">
<IMG SRC="galileo_out.gif" NAME="grafik"
onMouseover="over()" onMouseout="out()">
</A>
</BODY>
</HTML>
9.1.1 Zugriff auf Grafiken  
Es gibt mehrere Möglichkeiten, auf Grafiken in einem HTML-Dokument zuzugreifen:
|
document.grafikname |
|
document.images[grafikname] |
|
document.images[grafiknummer] |
Hierbei steht grafikname für den Wert des NAME-Attributs der jeweiligen Grafik und grafiknummer für die laufende Nummer der Grafik im HTML-Dokument. Da alle Grafiken im Array document.images[] gespeichert werden, beginnt die Zählung bei 0.
Im Beispielcode von oben kann man also die Grafik folgendermaßen ansprechen:
|
document.grafik |
|
document.images["grafik"] |
|
document.images[0] |
In der Regel entscheidet man sich für die erste Methode; achten Sie also darauf, Ihren Grafiken eindeutige und einprägsame NAME-Attribute zu geben, und verwenden Sie dabei keine gefährlichen Zeichen wie etwa Punkte (dann müsste man Methode 2 oder 3 zum Ansprechen der Grafik verwenden).
Die interessanteste und auch wichtigste Eigenschaft des Image-Objekts ist src: das ist die URL der Grafik. Mit JavaScript hat man sowohl Lese- als auch Schreibzugriff auf diese Eigenschaft – ändert man also ihren Wert, wird die neue Grafik geladen. Das Thema »Dynamisches HTML« ist allerdings erst in einem späteren Kapitel an der Reihe. Sie können so nur eine Grafik durch eine andere austauschen; Rahmenparameter wie etwa Breite, Höhe und Position der Grafik können nicht verändert werden. Sie sollten also stets gleich große Grafiken verwenden; andernfalls wird die neue Grafik gestaucht oder gestreckt dargestellt.
Somit sind zwei Zeilen des obigen Codes erklärt: Mit document.grafik.src="galileo_over.gif" wird die Grafik galileo_over.gif anstelle der alten Grafik geladen und dargestellt; analog erklärt sich die Wirkungsweise von document.grafik.src="galileo_out.gif".
Die beiden Funktionen over() und out() werden durch die Event-Handler onMouseover und onMouseout aufgerufen. Sie erinnern sich: Wie der Name schon andeutet, wird der erste Event-Handler tätig, wenn die Maus über das Objekt (hier: den Link) fährt, und onMouseout tritt in Aktion, wenn die Maus das Objekt (den Link) wieder verlässt.
|
Nur der Internet Explorer (ab Version 4) unterstützt die Event-Handler onMouseover und onMouseout beim <IMG>-Tag; wenn also nicht verlinkte Grafiken mit einem Mouseover-Effekt versehen werden sollen, muss um die Grafik ein Link (etwa mit HREF="#") herum gebaut werden. |
9.1.2 Prüfungen auf Kompatibilität  
|
Kommen wir zu einem der Hauptprobleme beim Einsatz des Image-Objekts im World Wide Web. Der Microsoft Internet Explorer 3 unterstützt dieses Objekt nicht (nur die Macintosh-Version, wie oben erwähnt), der Netscape Navigator 2 ebenfalls nicht. Das ist ein Problem, denn manche Webdesigner scheren sich nicht um die alten Browser und nehmen Fehlermeldungen bei manchen Nutzern in Kauf. Der Netscape Navigator 2 ist inzwischen kaum mehr verbreitet, aber der Internet Explorer 3 wird mit Windows 95 ausgeliefert und ist auch Bestandteil älterer Versionen der AOL-Zugangssoftware und damit durchaus noch weit verbreitet. |
Sie haben am obigen Codebeispiel schon gesehen, dass es recht viel Tipparbeit ist, die Funktionen für den Mouseover-Effekt zu programmieren (obwohl Sie ja noch gar nicht wissen, was die noch nicht erklärten Zeilen überhaupt sollen). Viel einfacher wäre doch ein Vorgehen wie folgt:
<A HREF="http://www.galileo-press.de"
onMouseover="document.grafik.src='galileo_over.gif'"
onMouseout="document.grafik.src='galileo_out.gif'">
<IMG SRC="galileo_out.gif" NAME="grafik">
</A>
Abbildung 9.2 Die Fehlermeldung eines Internet Explorer 3
|
Das bedeutet wenig Tipparbeit, aber viel Ärger, da es bei älteren Browsern eine Fehlermeldung gibt. Noch vor einem Jahr war ein Fehler dieser Machart auf erschreckend vielen Webseiten verbreitet, inzwischen hat das zum Glück abgenommen.
Nach und nach begannen die größeren Agenturen, Abfragen zu entwickeln, die überprüfen sollten, ob das Image-Objekt unterstützt wird. Eine erste Variante bestand darin, explizit zu prüfen, ob der Netscape Navigator 3 verwendet wird. Tolle Idee – aber dann tut sich beim Netscape Navigator 4 nichts.
Eine weitere Methode bestand darin, zu prüfen, ob der Netscape Navigator 3 oder 4, eventuell auch ob der Internet Explorer 4 zum Einsatz kommt. Folgender Code stammt – nur leicht abgewandelt – von einer bekannteren Website:
if (navigator.appName.indexOf("Netscape")!=-1){ //Netscape-Browser
nn3 = (navigator.userAgent.indexOf("Mozilla/3.0") != -1)
nn4 = (navigator.userAgent.indexOf("Mozilla/4.0") != -1)
if (nn3||nn4){
//JavaScript-Code mit Image-Objekt
}
}
Das war schon besser, aber der Internet Explorer-Teil wurde oft vergessen, und an die Macintosh-Version des Internet Explorer 3 wurde sowieso nicht gedacht. Besonders schlimm wurde es jedoch, als Netscape die Version 4.5 veröffentlichte: navigator.userAgent enthielt nun die Zeichenfolge "Mozilla/4.5", und damit bekamen ausgerechnet die Anwender mit dem neuesten Browser den grafischen Effekt überhaupt nicht zu sehen.
Mit etwas Aufwand kann man eine etwas kompatiblere Abfrage programmieren – beispielsweise die Versionsnummer auslesen, und beim Netscape auf »größer oder gleich 3« sowie beim Internet Explorer auf »Macintosh und größer oder gleich 3 oder andere Plattform und größer oder gleich 4« prüfen. Aber es gibt ja noch den Opera-Browser, den Webbrowser in Star Office 5, den HotJava-Browser von Sun ...
Eine Methode kommt unserem Ziel schon recht nahe. Das Image-Objekt ist Teil des JavaScript-1.1-Sprachschatzes; man könnte also folgendes schreiben:
<SCRIPT LANGUAGE="JavaScript"><!--
function over() {} //Dummy-Funktion für alte Browser
function out() {}
//--></SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.1"><!--
function over(){
document.grafik.src = "galileo_over.gif"
}
function out(){
document.grafik.src = "galileo_out.gif"
}
//--></SCRIPT>
Diesen Trick kennen Sie schon von früher – Browser, die nur JavaScript 1.0 unterstützen, führen die leeren Dummy-Funktionen over() und out() aus, tun also effektiv nichts. Browser, die jedoch auch JavaScript 1.1 unterstützen, sehen beide Versionen der Funktionen – und führen die jeweils letzte Version aus. Das funktioniert sehr gut, nur eben beim Internet Explorer 3 für Macintosh nicht.
Es gibt jedoch noch eine Methode. Sie erinnern sich vielleicht an die Überprüfung, ob ein Fenster die Methode focus() besitzt (ab JavaScript 1.1 nämlich: if (window.focus) { ... }. Hierbei werden Objekte und Methoden, die es gibt, in einer if-Abfrage als true ausgewertet, ansonsten als false. Erinnern Sie sich noch an die beiden anderen Möglichkeiten, auf eine Grafik im HTML-Dokument zuzugreifen? Über das Array document.images[] ist das recht einfach möglich. Es liegt also nahe, folgende Abfrage zu verwenden:
if (document.images){
//JavaScript-Code mit Image-Objekt
}
Diese Methode hat sich mittlerweile durchgesetzt – hat auch lange genug gedauert – und sie ist zudem fast perfekt. Fast perfekt nur deswegen, weil ältere Versionen vom Opera-Browser (bis etwa 3.21) document.images in einer if-Abfrage als false auswerten, obwohl der norwegische Mini-Browser das Image-Objekt vollständig unterstützt. Hierfür aber noch eine weitere Abfrage zu programmieren ist wirklich zu viel des Guten.
9.2 Animierte JPEGs  
Sie kennen sicherlich die Basis aller nervenden Werbebanner im World Wide Web – animierte GIF-Grafiken. Als das Grafikformat 1987 bzw. 1989 im Auftrag der Firma CompuServe entwickelt wurde, dachte noch niemand daran, dass dieses geheime Feature – nämlich die Integration mehrerer Einzelgrafiken in eine GIF-Datei – später einmal Furore machen sollte. Leider ist das nur mit GIF-Grafiken möglich, die ja auf nur 256 Farben beschränkt sind. Fotorealistische Animationen sind damit nicht möglich oder nur in sehr schlechter Qualität.
Mit JavaScript kann dieser Missstand behoben werden, und zwar mit dem Image-Objekt. Nach Ablauf einer bestimmten Zeitdauer wird einfach das nächste Bild der Animation eingeblendet. Nachfolgend finden Sie den Code, der die Animation mit einer Verzögerung von einer Sekunde zwischen den Einzelbildern auf den Bildschirm bringt.
<HTML>
<HEAD>
<TITLE>JPEG-Animation</TITLE>
</HEAD>
<SCRIPT LANGUAGE="JavaScript"><!--
if (document.images)
var bilder = new Array("ani1.jpg", "ani2.jpg", "ani3.jpg",
"ani4.jpg")
var naechstesBild = 0 //Array-Index der nächsten Bildes
var verzoegerung = 1000 //Verzögerung in Millisekunden
function animation(){
document.ani.src = bilder[naechstesBild]
naechstesBild++
if (naechstesBild==bilder.length)
naechstesBild = 0
setTimeout("animation()", verzoegerung)
}
//--></SCRIPT>
</HEAD>
<BODY onLoad="if (document.images) animation()">
<H3>Animation</H3>
<IMG SRC="ani1.jpg" NAME="ani">
</BODY>
</HTML>
9.2.1 Eine Animation mit JavaScript  
Für das Skript werden ein paar globale Variablen benötigt:
if (document.images)
var bilder = new Array("ani1.jpg", "ani2.jpg", "ani3.jpg",
"ani4.jpg")
var naechstesBild = 0 //Array-Index der nächsten Bildes
var verzoegerung = 1000 //Verzögerung in Millisekunden
Beachten Sie, dass der (für Netscape 2) kritische Befehl, nämlich der Zugriff auf das Array-Objekt, mit if (document.images) eingeschlossen wird. Das Array wird nur benötigt, wenn der Browser das Image-Objekt unterstützt (und dann kann er auch JavaScript-Arrays erzeugen). In dem Array bilder stehen die Dateinamen der einzelnen Grafiken der JPEG-Animation. In der Variablen naechstesBild wird der Array-Index des nächsten Bildes der Animation gespeichert. Angefangen wird beim ersten Bild. Es besitzt den Index 0, also enthält die Variable den Startwert 0. Die letzte Variable, die die Verzögerung in Millisekunden zwischen den einzelnen Animationsschritten enthält, müsste eigentlich nicht als global deklariert werden. Behalten Sie aber immer im Hinterkopf, dass Ihre Skripten von Leuten bearbeitet
werden könnten, die sich nicht so gut in JavaScript auskennen. In so einem Fall ist es gut, wenn wichtige Parameter für das Skript an markanter Stelle stehen, so dass dort auch unbedarftere Kollegen Änderungen vornehmen können, ohne gleich den gesamten Programmcode zu ruinieren.
Fehlt nur noch die Funktion animation(), die das nächste Bild der Animation anzeigt (der Index wird aus der Variablen naechstesBild gewonnen) und sich selbst nach einiger Zeit (diese steht in verzoegerung) wieder aufruft. Davor muss natürlich noch die Nummer des nächsten Bildes um eins erhöht werden. Sollte man beim letzten Bild angekommen sein, so soll die Animation wieder von vorn beginnen, naechstesBild wird also auf 0 gesetzt.
function animation(){
document.ani.src = bilder[naechstesBild]
naechstesBild++
if (naechstesBild==bilder.length)
naechstesBild = 0
setTimeout("animation()", verzoegerung)
}
Sie fragen sich, wo die Abfrage bleibt, ob der Browser das Image-Objekt überhaupt unterstützt? Der Trick besteht hier darin, dass die Funktion animation() ja erst einmal aufgerufen werden muss – und zwar mit dem onLoad-Event-Handler des <BODY>-Tags. Dort wird auch geprüft, ob der Bildertausch unterstützt wird:
<BODY onLoad="if (document.images) animation()">
9.2.2 Bilder in den Cache laden  
Wenn Sie dieses oder das letzte Beispiel auf der heimischen Festplatte oder auf dem lokalen Webserver ausprobieren, funktioniert alles wunderbar. Sobald Sie die Seiten aber in das World Wide Web stellen, werden Sie wahrscheinlich eine üble Überraschung überlegen: Es dauert unter Umständen mehrere Sekunden, bis die neue Grafik erscheint. Der Grund dafür ist schnell gefunden: Die neuen Grafiken müssen ja erst einmal vom Browser angefordert und über das Internet übertragen werden. Je nach Qualität der Verbindung kann das schon seine Zeit dauern. Zwar können Sie mit JavaScript die Ladegeschwindigkeit des Browsers nicht verbessern, aber Sie können wenigstens so tun. Ist eine Grafik nämlich einmal geladen, so liegt sie im Speicher- oder Festplattencache des Browsers. Der Trick ist nun, die Grafiken nicht erst zu laden, wenn sie angezeigt werden sollen, sondern schon vorher. Das ist sogar in HTML möglich, man muss nur
darauf achten, dass die Grafiken nicht optisch stören. Der erste Versuch, Höhe und Breite der Grafik auf 0 zu setzen, schlägt fehl, da die meisten Browser das ignorieren. Also muss man das nächstkleinere Maß nehmen: 1 x 1 Pixel.
<IMG SRC="ani2.jpg" WIDTH=1 HEIGHT=1>
<IMG SRC="ani3.jpg" WIDTH=1 HEIGHT=1>
<IMG SRC="ani4.jpg" WIDTH=1 HEIGHT=1>
Wollen Sie komplett vermeiden, dass die Grafiken sichtbar oder erahnbar sind, müssen Sie in die JavaScript-Trickkiste greifen. Das Image-Objekt hilft Ihnen hierbei. Mit dem folgenden Befehl wird ein Image-Objekt erzeugt, dessen src-Eigenschaft auf "ani2.jpg" gesetzt wird. Der besondere Effekt hierbei: Die Grafik wird vom Browser im Hintergrund geladen und liegt damit im Cache.
var img = new Image()
img.src = "ani2.jpg"
Wenn man ganz sauber programmieren will, gibt man dem Konstruktor des Image-Objekts noch die Breite und Höhe der Grafik mit, was aber in der Praxis überhaupt keine Auswirkungen hat:
var img = new Image(breite, hoehe)
img.src = "ani2.jpg"
Die Instanz des Image-Objekts befindet sich nun im Speicher, und die Grafik wird vom Webserver angefordert, aber noch gar nicht angezeigt. Umgangssprachlich spricht man hier vom Vorladen oder Preloading. Zuerst wird das Beispiel aus dem vorherigen Abschnitt umgearbeitet:
<HTML>
<HEAD>
<TITLE>Bildlein-Wechsle-Dich</TITLE>
<SCRIPT LANGUAGE="JavaScript"><!-
function preload(){
img_over = new Image()
img_over.src = "galileo_over.src"
}
function over(){
if (document.images)
document.grafik.src = img_over.src
}
function out(){
if (document.images)
document.grafik.src = "galileo_out.gif"
}
preload()
//--></SCRIPT>
</HEAD>
<BODY>
<A HREF="http://www.galileo-press.de">
<IMG SRC="galileo_out.gif" NAME="grafik">
</A>
</BODY>
</HTML>
Beim Laden des HTML-Dokuments wird die JavaScript-Funktion preload() aufgerufen, die die Grafik galileo_over.gif in den Cache des Browsers lädt. Die Grafik galileo_out.gif muss nicht extra geladen werden, sie wird ja im HTML-Dokument schon angezeigt.
Beachten Sie auch die folgende neue Zeile in der Funktion over():
document.grafik.src = img_over.src
Die src-Eigenschaft der Grafik mit dem NAME-Attribut "grafik" wird also auf die src-Eigenschaft der Variablen img_over gesetzt. Das bringt zwar in diesem Beispiel keine Vorteile, aber man sollte das Verfahren einmal gesehen haben. Wenn die Grafikabteilung Ihrer Firma mal wieder besonders kreative Dateinamen gewählt hat, kann dieses Vorgehen durchaus nützlich sein, indem Sie dann wenigstens brauchbare Variablennamen vergeben können.
Die Animation kann ebenso schnell angepasst werden. Da aber schöner Code herauskommen soll, wird hier ein wenig mehr Aufwand getrieben. Die Namen und auch die URLs der einzelnen Grafiken sollen in Variablen gespeichert werden. Dies hat wieder den Vorteil, dass ein größerer Personenkreis das Skript bearbeiten kann, indem einfach die Werte der globalen Variablen geändert werden.
Im ersten Schritt soll eine Funktion geschrieben werden, die eine Reihe von Grafiken in den Cache lädt. Die URLs der Grafiken werden dabei als Parameter an die Funktion übergeben. Wie man die Anzahl der übergebenen Parameter ermittelt, haben Sie ja schon am Beispiel von Make Array() gesehen. Der Rest ist schnell geschrieben:
function preload(){
var img
for (var i=0; i<preload.arguments.length; i++)
img = new Image()
i.src = preload.arguments[i]
}
}
Diese Funktion lädt alle übergebenen Grafiken in den Cache. Leider kann man nicht über den Variablennamen auf die Grafik zugreifen, da lediglich eine Variable verwendet wird. Ohne Fleiß kein Preis: In diesem Beispiel sollen die Image-Variablen für die Animation auch in einem Array gespeichert werden. Diese Variablen sollen auch von anderen Funktionen aus angesprochen werden können, müssen also als global definiert werden.
var bildvariablen = new Array()
function preload(){
var img
for (var i=0; i<preload.arguments.length; i++){
img = new Image()
img.src = preload.arguments[i]
bildvariablen[i] = img
}
}
Schließlich muss nur noch die Funktion animation() angepasst werden – hier wird auf die Variablen der Grafiken zugegriffen und nicht direkt auf deren URL.
function animation(){
document.ani.src = bildvariablen[naechstesBild]
naechstesBild++
if (naechstesBild==bilder.length)
naechstesBild = 0
setTimeout("animation()", verzoegerung)
}
9.3 Animierte Navigation  
Der letzte Teil dieses Kapitels ist einem etwas umfangreicheren Beispiel gewidmet: einer animierten Navigation. Die Kernidee besteht hierbei darin, dass der Benutzer wie beim Menü eines Browsers die Oberbegriffe oder Kategorien angezeigt bekommt. Fährt er mit der Maus über einen der Begriffe, klappt das Menü nach unten aus, und er kann einen der Menüpunkte auswählen. In Abbildung 9.3 sehen Sie, wie das Beispielmenü aussieht, wenn alle Kategorien ausgeklappt sind; in Abbildung 9.4 sehen Sie, wie das Menü in nichtausgeklapptem Zustand aussieht. Wie Sie ja bereits wissen, lässt sich (mit den bisher bekannten JavaScript-Mitteln) keine Grafik irgendwo auf einer HTML-Seite platzieren. Sie müssen also für jede Stelle des ausgeklappten Menüs eine Füllgrafik (am besten als transparentes GIF) verwenden.
Abbildung 9.3 Das vollständig ausgeklappte Menü
|
Abbildung 9.4 Das vollständig eingeklappte Menü
|
Folgende Punkte sind nun nacheinander abzuarbeiten:
|
Fährt der Benutzer mit der Maus über eine Kategorie, so muss diese ausgeklappt werden (und natürlich eventuell ausgeklappte Kategorien wieder eingeklappt werden). |
|
Klickt der Benutzer auf einen einzelnen Menüpunkt, so soll die entsprechende HTML-Seite aufgerufen werden. Dazu muss natürlich überprüft werden, ob die entsprechende Kategorie überhaupt ausgeklappt ist. Denken Sie daran, dass der HTML-Link vorhanden ist, egal ob gerade ein Menüpunkt angezeigt wird oder statt dessen nur die transparente Füllgrafik erscheint. |
9.3.1 Vorüberlegungen  
Den kompletten HTML- und JavaScript-Code für dieses Beispiel finden Sie am Ende dieses Kapitels. Im Folgenden werden nur die wesentlichen Elemente vorgestellt. Bevor Sie sich ans Programmieren machen, sollten Sie sich Gedanken über Konventionen für die Dateinamen und NAME-Attribute der Grafiken machen; dann sparen Sie sich später einiges an Programmieraufwand. Auch die Namen der JavaScript-Funktionen sollten wohlüberlegt sein. Folgenden Vorschlag kann ich Ihnen anbieten:
|
In diesem Beispiel gibt es zwei Kategorien, die erste mit drei Unterpunkten, die zweite mit vier Unterpunkten. Die Menügrafiken haben den Namen menu_X_Y.gif und das NAME-Attribut menu_X_Y, wobei X die Nummer der Kategorie ist (also 1 oder 2), und Y ist die Nummer des Unterpunkts (von 1 bis 3 bzw. von 1 bis 4). Die transparente Grafik (am besten 1 x 1 Pixel groß, dann ist sie kleiner) heißt leer.gif. Die Oberbegriffe der Kategorien haben den Namen menu_X, wobei X wiederum die Nummer der Kategorie ist. |
|
Wenn die Maus über einen Kategorienamen fährt, wird die JavaScript-Funktion klapp_auf(X) aufgerufen; wenn der Mauszeiger die Grafik wieder verläßt, wird klapp_zu(X) aufgerufen. Auch hier bezeichnet X wieder die Nummer der Kategorie. |
|
Wenn die Maus über einen einzelnen Menüpunkt fährt, wird die JavaScript-Funktion menue_in(X, Y) aufgerufen, beim Verlassen menue_out (X, Y). Wenn der Benutzer auf einen Menüpunkt klickt, wird die Funktion click(X, Y) aufgerufen. X und Y stehen – erraten – für die Kategorienummer und die Menüpunktnummer. |
In globalen Variablen werden die folgenden Werte gespeichert:
|
Die Nummer der aktuell ausgeklappten Kategorie |
|
Die URLs, auf die die einzelnen Menüpunkte weisen |
|
Außerdem noch eine ID, die – der aufmerksame Leser wird es wissen – auf die Verwendung von Timeouts hindeutet |
Die globalen Informationen sollten wieder in einem exponierten Block des Dokuments abgelegt werden, damit auch andere Programmierer diese verändern können.
Beginnen wir also mit den globalen Variablen:
if (document.images){
var urls = new Array(
new Array("seite1-1.htm", "seite1-2.htm", "seite1-3.htm"),
new Array("seite2-1.htm", "seite2-2.htm", "seite2-3.htm",
"seite2-4.htm")
)
var ausgeklappt = 0 //Startwert für ausgeklappte Rubrik
var ID
}
9.3.2 Auf- und Zuklappen  
Die Kernfunktion dieses Programms besteht im Auf- und Zuklappen der einzelnen Kategorien. Das ist jedoch gar nicht so schwer. Das Zuklappen ist hierbei besonders einfach: Jede der Kategoriegrafiken wird durch eine Füllgrafik ersetzt. Erinnern Sie sich daran, dass die NAME-Attribute der Menüpunkte das Format menu_X_Y haben, wobei X die Kategorienummer und Y die Menüpunktnummer ist. Die Anzahl der einzelnen Menüpunkte bekommt man aus dem Array urls[]; bedenken Sie hierbei, dass man auf urls[1] zugreifen muss, wenn man sich mit der zweiten Kategorie beschäftigen will.
function klapp_zu(X){
if (document.images){
ausgeklappt = 0 //globale Variable zurücksetzen
for (var i=1; i<=urls[X-1].length; i++) //alle Menüpunkte
eval("document.menu_"+X+"_"+i+".src = 'leer.gif'")
}
}
Beim Aufklappen muss man einige kleine Hürden umgehen. Als Erstes muss die gerade aufgeklappte Kategorie wieder zugeklappt werden, und dann müssen die einzelnen Grafiken an die richtige Stelle gesetzt werden.
function klapp_auf(X){
if (document.images){
if (ausgeklappt !=0 && ausgeklappt!=X)
klapp_zu(ausgeklappt)
ausgeklappt = X //erst jetzt, nach dem Zuklappen, setzen!
for (var i=1; i<=urls[X-1].length; i++)
eval("document.menu_"+X+"_"+i+".src = 'menu_"+X+"_"+
i+".gif'")
}
}
9.3.3 Die einzelnen Menüpunkte  
Was passiert, wenn sich die Maus über einem Menüpunkt befindet, und was passiert vor allem, wenn die Maus den Menüpunkt wieder verlässt? Im Gegensatz zu den meisten Menüs einer grafischen Benutzeroberfläche sollte das Menü wieder eingeklappt werden, wenn die Maus das Menü verlässt. Da man (zumindest mit den bisher bekannten Mitteln) nicht feststellen kann, ob der Benutzer den Menüpunkt nach oben verlässt (dann kann das Menü natürlich aufgeklappt bleiben) oder nach links oder rechts, sollte es zuklappen. Aus diesem Grund ist folgendes Vorgehen empfehlenswert: Wenn der Benutzer einen Menüpunkt verlässt, wird die Funktion klapp_zu() aufgerufen. Um aber zu verhindern, dass das Menü auch zugeklappt wird, wenn die Maus nach oben oder unten fährt, wird via Timeout diese Funktion erst nach einer kurzen Zeitspanne (etwa 50-100 Millisekunden) aufgerufen. Wenn die Maus über einen Menüpunkt fährt, wird dieser Timeout wieder eliminiert.
Beachten Sie, dass die Funktion klapp_auf() auch abgeändert wird, da die Funktion auch gegebenenfalls eine andere aktive Sektion zuklappt.
|
Ein wichtiger Hinweis noch, der vor allem bei älteren Browsern1 und manchen Beta-Versionen von Browsern beachtet werden sollte: Wenn innerhalb von kurzer Zeit zwei verschiedene Event-Handler aktiv werden (in diesem Fall etwa onMouseout bei den vorherigen Menüpunkten und onMouseover bei dem neuen Menüpunkt), ist es hin und wieder vorgekommen, dass einer der beiden Event-Handler (zumeist der zuerst aktive, also onMouseout) verschluckt wurde. Aus diesem Grund empfiehlt es sich, auch bei onMouseover die alte Sektion zuzuklappen. |
Fassen wir diese Punkte zusammen:
|
In der Funktion menue_in() wird der Timeout gelöscht und gegebenenfalls die vorherige Rubrik zugeklappt |
|
In der Funktion menue_out() wird ein Timeout gesetzt, der die Sektion zuklappt. |
|
In der (bereits programmierten) Funktion klapp_auf() muss der Timeout ebenfalls gelöscht werden. |
function menue_in(X, Y){
if (document.images){
clearTimeout(ID)
if (X!=ausgeklappt)
klapp_zu(X)
}
}
function menue_out(X, Y){
if (document.images)
eval("ID = setTimeout('klapp_zu("+X+")', 50)")
}
function klapp_auf(X){
if (document.images){
clearTimeout(ID)
if (ausgeklappt !=0 && ausgeklappt!=X)
klapp_zu(ausgeklappt)
ausgeklappt = X //erst jetzt, nach dem Zuklappen, setzen!
for (var i=1; i<=urls[X-1].length; i++)
eval("document.menu_"+X+"_"+i+".src = 'menu_"+X+"_"+i+
".gif'")
}
}
9.3.4 Verlinkung der Menüpunkte  
Dieser Punkt ist wohl der einfachste in diesem Beispiel, man muss nur beachten, dass der Array-Index bei 0 beginnt, die Kategorien jedoch bei 1 beginnen. Ebenso muss der Menüpunkt natürlich eingeblendet sein, damit der Link aktiv ist.
function click(X, Y){
if (X==ausgeklappt)
window.location.href = (urls[X-1])[Y-1]
}
9.3.5 Einbau in die HTML-Datei  
So schön das Beispiel (hoffentlich) auch bei Ihnen funktionieren wird, so schlecht sieht es aus, wenn Sie einen älteren Browser verwenden oder JavaScript deaktivieren. Aus diesem Grund folgt hier eine Strategie, wie man ein möglichst großes Publikum bedient. Natürlich könnten Sie eine JavaScript-Version und eine HTML-Version der Navigation erstellen, aber in diesem Fall lohnt es sich, alles auf einer Seite zu halten.
|
Im HTML-Dokument selbst sind alle Kategorien ausgeklappt. Damit werden zum einen alle Grafiken schon einmal in den Cache geladen, und zum anderen bekommen auch ältere Browser alles zu sehen. |
|
Nach dem Laden des Dokuments wird versucht, alle Kategorien zuzuklappen. Dies wird nur von Browsern erledigt, die JavaScript aktiviert haben und das Image-Objekt auch unterstützen. Der entsprechende Code sieht folgendermaßen aus: |
<BODY onLoad="for (var i=1;i<=urls.length;i++) klapp_zu(i)">
|
Die einzelnen Menüpunkte benutzen nicht das JavaScript-Pseudoprotokoll, um die Funktion click() aufzurufen, sondern den Event-Handler onClick. Das HREF-Attribut wird auf die korrekte (HTML-)Zieladresse gesetzt. Damit aber neuere Browser nicht die HTML-Seite aufrufen, endet der onClick-Event-Handler mit return false, wenn der Browser das Image-Objekt kennt. Sie wissen schon aus vorherigen Kapiteln, dass dann der HTML-Link nicht ausgeführt wird. Dies ist mit einem relativ langen Event-Handler zu erledigen; um sich aber Schreibarbeit zu sparen, sollte der onClick-Event-Handler auf return img() enden. Die Funktion img() sieht folgendermaßen aus: |
function img(){
return (document.images) ? false : true
}
9.4 Erweiterung der Navigation  
Die Navigation funktioniert ja schon ganz gut, aber ganz zufrieden sind wir noch nicht. Zum einen soll die Navigation auch auf Unterseiten verwendet werden; der Link der betreffenden Unterseite auf sich selbst sollte dann natürlich inaktiv sein. Aber ein kleiner Effekt soll zusätzlich noch eingebaut werden: Fährt der Benutzer mit der Maus über einen Menüpunkt, so soll dieser fett dargestellt werden. Der Menüpunkt, der die gerade aktive Seite bezeichnet, soll von vornherein fett sein.
9.4.1 Vorbereitungen  
Was muss geändert werden, damit das so funktioniert?
|
Die Funktion menue_in() muss dahingehend geändert werden, dass der aktive Menüpunkt in fetter Schrift dargestellt wird. Ist der aktive Menüpunkt gleichzeitig der geladene Menüpunkt, so muss keine Aktion erfolgen (wir wollen ja effizient programmieren). |
|
Die Funktion menue_out() muss dahingehend geändert werden, dass der zuvor aktive Menüpunkt wieder normal dargestellt wird – außer natürlich, es handelt sich um den aktiven Menüpunkt, der muss fett bleiben. |
|
Die Funktion klapp_auf() muss dahingehend geändert werden, dass der geladene Menüpunkt auch fett dargestellt wird. |
|
Die Funktion click() muss dahingehend geändert werden, dass beim Klicken auf der gerade aktiven Menüpunkt nichts passiert. |
Auch hierzu sind ein paar Vorbereitungen notwendig. Da die Menüpunkte Grafiken sind, handelt es sich bei den in fetter Schrift dargestellten Menüpunkten ebenfalls um Grafiken. Diese haben folgende Namenskonvention: Der fette Menüpunkt in Kategorie Nummer X und (innerhalb dieser Kategorie) Nummer Y hat den Grafiknamen menu_X_Y_f.gif (f steht für fett).
Schließlich werden zwei weitere globale Variablen eingeführt, die dann auch von anderen Leuten geändert werden können: die Nummer der Kategorie und die Nummer des Menüpunkts, der gerade geladen ist. Auf jeder Unterseite können und müssen diese Nummern individuell angepasst werden. Es ist aber einfacher, zwei Zahlenwerte zu ändern, als irgendwelche Parameter innerhalb der JavaScript-Funktionen. Folgendermaßen würde es aussehen, wenn der zweite Menüpunkt der ersten Kategorie geladen ist:
var kategorie = 1
var menuepunkt = 2
9.4.2 Leichte Änderungen  
Der letzte Punkt unserer To-Do-Liste – der inaktive Link – ist auch gleichzeitig der einfachste. Es wird einfach überprüft, ob der entsprechende Menüpunkt angeklickt worden ist, und falls ja, passiert eben nichts.
function click(X, Y){
if (X==ausgeklappt && (X!=kategorie||Y!=menuepunkt))
window.location.href = (urls[X-1])[Y-1]
}
Beim Ausklappen ist auch eine Kleinigkeit zu beachten: Beim geladenen Menüpunkt muss die fette Variante der Grafik angezeigt werden. Das war dann aber auch schon alles, worüber man stolpern könnte. Unter der lokalen Variablen suffix wird gespeichert, was an den Grafiknamen angehängt werden muss. Das ist "_f", wenn es sich um den geladenen Menüpunkt handelt, ansonsten nichts.
function klapp_auf(X){
if (document.images){
clearTimeout(ID)
if (ausgeklappt !=0 && ausgeklappt!=X)
klapp_zu(ausgeklappt)
ausgeklappt = X //erst jetzt, nach dem Zuklappen, setzen!
for (var i=1; i<=urls[X-1].length; i++){
suffix = (X==kategorie&&i==menuepunkt) ? "_f" : ""
eval("document.menu_"+X+"_"+i+".src = 'menu_"+X+"_"+i+
suffix+".gif'")
}
}
}
9.4.3 Doppeltes Mouseover  
Die Funktion menue_in() wird folgendermaßen geändert: Ist der aktive Menüpunkt gleichzeitig der geladene Menüpunkt, findet kein Mouseover statt; ansonsten wird die fette Variante des aktiven Menüpunkts dargestellt.
function menue_in(X, Y){
if (document.images){
clearTimeout(ID)
if (X!=ausgeklappt)
klapp_zu(X)
else
if (X!=kategorie||Y!=menuepunkt)
eval("document.menu_"+X+"_"+Y+".src = 'menu_"+X+"_"+Y+
"_f.gif'")
}
}
In der Funktion menue_out() wird statt der fetten nun die normale Variante des Menüpunktes angezeigt, außer es handelt sich um den geladenen Menüpunkt.
Abbildung 9.5 Die erweiterte Version: Der aktuelle Menüpunkt wird fett dargestellt.
|
function menue_out(X, Y){
if (document.images){
if (X!=kategorie||Y!=menuepunkt)
eval("document.menu_"+X+"_"+Y+".src = 'menu_"+X+"_"+Y+
".gif'")
eval("ID = setTimeout('klapp_zu("+X+")', 50)")
}
}
9.4.4 Das komplette Beispiel im Überblick  
Auf der Buch-CD finden Sie die gesamte Datei im Überblick, einschließlich des beispielhaften HTML-Codes. So können Sie sich einiges an Tipparbeit sparen.
9.5 Tipps aus der Praxis  
In diesem letzten Abschnitt dieses Kapitels über das Image-Objekt möchte ich Ihnen noch ein paar Tipps aus der Praxis geben, damit Sie Fehlermeldungen vermeiden oder Ihre Seiten mit noch mehr Funktionalität versehen können. Das Image-Objekt wird sehr gerne eingesetzt, und ein paar Fehler werden immer wieder gemacht, ein paar Fragen werden immer wieder gestellt. Die folgenden zwei Abschnitte beantworten hoffentlich Ihre offenen Fragen.
9.5.1 Vorladen – aber richtig  
Sie haben sich vielleicht gefragt, warum beim Beispiel zum Vorladen die Funktion preload() nicht mittels <BODY onLoad="preload()"> aufgerufen worden ist, sondern noch im <HEAD>-Abschnitt des HTML-Dokuments. Das hat einen einfachen Grund. Gehen Sie von einer langsameren Verbindung oder von einem umfangreichen Dokument aus. Ein Teil des HTML-Dokuments wurde schon übertragen und ist auch schon vom Browser dargestellt worden. Hier ist besonders der Internet Explorer sehr schnell, während der Netscape Navigator Tabellen erst dann anzeigt, wenn sie vollständig übertragen worden sind. Aber zurück zum Beispiel: Ein Teil der Seite ist also schon geladen, wird dargestellt – und die Event-Handler in diesem Teil sind natürlich aktiv. Wie es der Zufall so will, fährt der Benutzer jetzt schon über eine mit Mouseover versehene Grafik. Im Beispiel
von oben würde nun die folgende Funktion aufgerufen werden:
function over(){
if (document.images)
document.grafik.src = img_over.src
}
Die Variable img_over wird aber erst in der Funktion preload() korrekt gesetzt. Das ist schlecht, wenn die Funktion erst mit dem onLoad-Event-Handler aufgerufen wird; wie Sie sich erinnern, tritt dieser erst in Aktion, wenn das gesamte HTML-Dokument übertragen worden ist. Es gäbe also in diesem Fall unter Umständen eine Fehlermeldung. Um dies zu vermeiden, rufen Sie Ihre Vorladefunktion noch vor dem ersten <IMG>-Tag auf!
9.5.2 Ladestand einer Grafik  
|
JavaScript Version 1.1 bietet dem Benutzer eine bequeme Möglichkeit festzustellen, wie weit eine Grafik schon geladen ist. Interessant wird das beispielsweise bei der Diashow aus dem Kapitel »Fenster II«. Wenn Sie die Diashow über eine langsame Netzwerkverbindung abspielen, kann es sein, dass umfangreiche Grafiken auf den einzelnen Seiten der Show noch nicht vollständig geladen sind, bevor die nächste Seite der Show schon angezeigt wird. Es wäre hier also praktisch, wenn die Grafiken alle schon vorher im Browser-Cache wären. Auch beim Beispiel mit der JPEG-Animation aus diesem Kapitel ist das wünschenswert. Wir wollen für das letztere Beispiel eine Lösung konstruieren, die analog auch für das Diashow-Beispiel eingesetzt werden kann. |
Der Trick besteht darin, eine Einstiegsseite zu programmieren, auf der alle Grafiken schon einmal geladen werden. Nun könnte man einerseits den Benutzer nach Ablauf einer gewissen Zeitspanne auf die nächste Seite weiterleiten, aber das ist keine sehr flexible Lösung. Bei einer langsamen Verbindung reicht die Zeit nicht aus, und bei einer schnellen Verbindung dauert sie zu lange.
Ein erster Ansatz besteht darin, den Event-Handler onLoad zu benutzen. Den gibt es auch bei Grafiken. Sobald bei jeder Grafik der onLoad-Event-Handler ausgeführt worden ist, kann auf die nächste Seite weiterverzweigt werden. Zudem sollte man nach einer bestimmten Zeit eh zur nächsten Seite übergehen, um die Geduld des Benutzers nicht allzu sehr zu strapazieren.
<HTML>
<HEAD>
<META HTTP-EQUIV="refresh" CONTENT="30;url=animation.html">
<!-- nach 30 Sekunden wird die Seite animation.html geladen -->
<TITLE>Animation vorbereiten</TITLE>
<SCRIPT LANGUAGE="JavaScript"><!--
var geladen = 0 //Anzahl der fertig geladenen Grafiken
function fertig(){
geladen++
if (geladen==4) //wenn alle Grafiken geladen sind
location.href = "animation.html"
}
//--></SCRIPT>
</HEAD>
<BODY>
<H3>Animation lädt... Bitte warten...</H3>
<IMG SRC="ani1.jpg" onLoad="fertig()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani2.jpg" onLoad="fertig()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani3.jpg" onLoad="fertig()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani4.jpg" onLoad="fertig()" WIDTH=1 HEIGHT=1>
</HTML>
Jedes Mal, wenn eine Grafik fertig geladen worden ist, wird ein Zähler um eins erhöht, und es wird überprüft, ob damit die magische Zahl 4 (die Anzahl der Grafiken) erreicht worden ist.
Dieses Vorgehen funktioniert unter gewissen Umständen nicht – vor allem auf älteren Rechnern mit wenig Speicher. Wenn zwei Grafiken quasi gleichzeitig vollständig geladen worden sind und dadurch die Funktion fertig() zweimal aufgerufen wird, kann es vorkommen, dass einer der Aufrufe verschluckt wird. Wie gesagt, das kommt ganz selten vor, aber es gibt Zeugen ...
Hier hilft Ihnen vielleicht das Attribut complete eines Image-Objekts. Dieses gibt an, ob eine Grafik vollständig geladen worden ist (true) oder nicht (false). In der Funktion fertig() wird also jetzt kein Zähler erhöht, sondern überprüft, ob alle Grafiken vollständig geladen worden sind. Falls ja, wird zur Animationsseite übergegangen.
Im folgenden Listing lernen Sie außerdem noch eine Anwendungsmethode für die Event-Handler onAbort und onError kennen. Ersterer tritt dann in Aktion, wenn der Benutzer das Laden mit der Schaltfläche Stop abbricht; Letzterer wird aktiv, wenn ein Fehler bei der Übertragung auftritt (beispielsweise, wenn die Grafikdatei nicht existiert).
<HTML>
<HEAD>
<META HTTP-EQUIV="refresh" CONTENT="30;url=animation.html">
<!-- nach 30 Sekunden wird die Seite animation.htm geladen -->
<TITLE>Animation vorbereiten</TITLE>
<SCRIPT LANGUAGE="JavaScript"><!--
function fertig(){
if (document.images[0].complete && document.images[1].complete &&
document.images[2].complete && document.images[3].complete)
location.href = "animation.html"
}
function abbruch(){
alert("Sie wollen das Laden abbrechen? Na gut, wir leiten Sie
weiter...")
location.href="animation.html"
}
function fehler(){
alert("Beim Laden einer der Grafiken ist ein Fehler aufgetreten.
Wir leiten Sie trotzdem weiter...")
location.href="animation.html"
}
//--></SCRIPT>
</HEAD>
<BODY>
<H3>Animation lädt... Bitte warten...</H3>
<IMG SRC="ani1.jpg" onLoad="fertig()" onAbort="abbruch()"
onError="fehler()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani2.jpg" onLoad="fertig()" onAbort="abbruch()"
onError="fehler()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani3.jpg" onLoad="fertig()" onAbort="abbruch()"
onError="fehler()" WIDTH=1 HEIGHT=1>
<IMG SRC="ani4.jpg" onLoad="fertig()" onAbort="abbruch()"
onError="fehler()" WIDTH=1 HEIGHT=1>
</HTML>
Leider wird onError nicht mitgeteilt, welche der Grafiken beim Laden einen Fehler hatte. Man könnte diese Information aber als Parameter an die Funktion fehler() übergeben.
Abbildung 9.6 Die Meldung, die erscheint, wenn eine der Grafiken nicht existiert
|
9.6 Fragen & Aufgaben  
1. |
Schreiben Sie eine Seite, die mit dem Benutzer Katz und Maus spielt. Auf der Seite gibt es eine Grafik; wenn der Benutzer jedoch mit der Maus darüber fährt, verschwindet die Grafik und taucht an einer anderen Stelle wieder auf. |
2. |
Schreiben Sie das Programm aus Aufgabe 1 so um, dass der Benutzer etwas Zeit hat, die Grafik zu erwischen. Dafür verschwindet die Grafik zusätzlich nach einer Sekunde automatisch und taucht an einer anderen Stelle wieder auf. |
3. |
Die Navigation aus diesem Kapitel soll weiter vereinfacht und für andere leichter bearbeitbar gemacht werden. Dazu ist der HTML-Code für die Navigation auf jeder Seite, auch auf Unterseiten, identisch. Wie können Sie dieselbe Funktionalität wie zuvor sicherstellen? |
4. |
Schreiben Sie das letzte Beispiel aus diesem Kapitel so um, dass Sie den onLoad-Event-Handler nicht benötigen. |
1 Netscape Navigator 2, Internet Explorer 3 scheiden hier beide glücklicherweise aus, da sie mit dem Image-Objekt nichts anfangen können.
|