Anmerkung: Einige Teile dieses Artikel sind momentan noch unfertig, die Lücken sind jeweils gekennzeichnet.
- Hinweise zum Arbeiten mit diesem Dokument
- Problemstellung
- Erste CSS-Lösung: Anker als Blockelement
- Zweite CSS-Lösung: Generierter Inhalt als Blockelement
- Dritte CSS-Lösung: Relativ positionierter Anker
- Vierte CSS-Lösung: Negativer Außenabstand und kompensierender Innenabstand
- Fünfte CSS-Lösung: Anker mit oberem Innenabstand
- JavaScript-Teillösung: Wenn das Dokument mit einem Anker aufgerufen wird
- JavaScript-Teillösung: Wenn ein dokumentinterner Anker angesprungen wird
- JavaScript-Kombinationslösung: Automatisches Vergeben der Event-Handler
- Alternative JavaScript-Kombinationslösung
- Nachteile der JavaScript-Lösung
- Postskriptum
Hinweise zum Arbeiten mit diesem Dokument
Dieses Dokument setzt grundlegende Kenntnisse und aktives Verständnis der Bereiche HTML, CSS und JavaScript voraus. Die meisten verwendeten Codekonstrukte werden nur hinsichtlich ihrer Funktion im Zusammenhang, nicht hinsichtlich der Bedeutung der Bestandteile erläutert. Es wird auch nicht mit Anspruch auf Vollständigkeit auf die Grundlagen und Hintergründe vermittelnde Quellen verwiesen. Insbesondere werden die syntaktischen Feinheiten sprachlich durch Fachbegriffe unterschieden. Um sich mit der verwendeten, größtenteils wortwörtlich an die W3C-Spezifikationen angelehnten Terminologie vertraut zu machen, lohnt die Lektüre von Vokabular und Syntax von (X)HTML und Vokabular und Syntax von CSS.
Problemstellung
Beim Anspringen eines in einem HTML-Dokument vorhanden Linkankers (Sprungmarke, fragment idenfitier) wird zum entsprechenden Element geblättert, sodass dieses direkt oben im Anzeigebereich dargestellt wird, durch welchen das Dokument betrachtet wird. Wenn ein Dokument einen mit position:fixed
oben auf der Seite fest positionierten und den Dokumentinhalt überlappenden Bereich – das heißt, ein Element eventuell mit weiteren Elementen – enthält und dieser eine ausreichende Höhe hat, überlappt und verdeckt er den Linkanker und den zugehörigen Absatz. Beispieldokument zur Verdeutlichung des Problems.
Erste CSS-Lösung: Anker als Blockelement
Der folgende Lösungsansatz sowie die darauffolgenden gehen davon aus, dass Anker vor allem bei Überschriften und damit bei inhaltlich-thematischen Einschnitten eingesetzt werden, welche mit den Elementen h1
, h2
, h3
, h4
, h5
und h6
ausgezeichnet sind. Prinzipiell lassen sich die Konstrukte auch auf andere Elemente übertragen, die Beispiele sind jedoch so angelegt, dass sie insbesondere bei adressierbaren Überschriften effektiv einsetzen lassen.
- Das Überschriftelement selbst erhält ein
id
-Attribut mit dem Ankernamen als Attributwert. -
Ein
a
-Element mit einemname
-Attribut, welches den Anker darstellt, wird an den Anfang einer Kapitelüberschrift gestellt. Das heißt, es folgt direkt der Startmarke deshX
-Elements und ist erster Kindknoten.Für die Funktionsfähigkeit des Ankers ist prinzipiell lediglich das
a
-Element nötig. Es könnte auch im Falle von XHTML 1.0 das zusätzlich nötigeid
-Attribut erhalten. Dasid
-Attribut wird stattdessen im Überschriftelement untergebracht, um die Überschrift mit einen Attributselektor adressieren zu können, wie sich später zeigen werden. -
Der Inhalt des
a
-Elements bleibt bis auf ein geschütztes, später nicht sichtbares Leerzeichen (
oder 
) leer, das heißt, es umfasst nicht den Inhalt der Kapitelüberschrift.Wenn wie beschrieben vorgegangen wird, sieht der entstehende HTML-Code eines
h2
-Elements folgendermaßen aus:<h2 id="sprungstelle"><a name="sprungstelle"> </a>Beispielüberschrift</h2>
-
Über eine Gruppe von Attributselektoren wie
h2 a[name], h3 a[name], h4 a[name]
wird allena
-Elementen, welche innerhalb vonh2
-,h3
- oderh4
-Elementen liegen und einname
-Attribut besitzen, folgende CSS-Eigenschaften zugewiesen:display:block;
- Über die CSS-Eigenschaft
display
wird dasa
-Element, welches naturgemäß ein Inline-Element ist, wie ein Blockelement dargestellt. height:60px;
- Dem Blockelement kann nun über die CSS-Eigenschaft
height
eine feste Höhe zugewiesen werden. Diese Größe muss mindestens der Höhe entsprechen, welche der am obersten Rand des Anzeigebereichs fest positionierte Bereich im ungünstigsten Fall einnimmt (im Beispiel: 60px). Als Einheit kann bei pixelbasiertem Layoutpx
angegeben werden, genauso können aber auch relative Einheiten wieem
oder%
(Prozent) verwendet werden, falls die Höhe des fest positionierten Bereiches relativ zur zur dokumentweiten Schriftgröße angegeben ist.
Das Schema ließe sich natürlich auch auf Anker in
h5
- undh6
-Elementen sowie beliebigen anderen ausweiten./* Betrifft
a
-Elemente innerhalb vonhX
-Elementen mitname
-Attribut: */ h2 a[name], h3 a[name], h4 a[name] { display:block; height:60px; /* Dieser Wert kann variieren. */ }Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, dem Ankerelement ein blauer Rahmen und ein hellgrauer Hintergrund.
Die Verwendung von zusätzlichen Attributselektoren (
h2 a[name]
) anstatt einfachen Nachkommenselektoren (h2 a
) ist nötig, weil der Microsoft Internet Explorer (Windows) und andere veraltete Browser diesen Regelsatz ignorieren müssen, da sieposition:fixed
nicht interpretieren. Da bei diesen Browsern das beschriebene Problem nicht auftaucht, ist keine das Problem umgehende Lösung nötig. Durch den meist ebenfalls unbekannten Attributselektor wird die komplette CSS-Regel übergangen. Prinzipiell besteht jedoch keine vorhersehbare Verbindung zwischen der Unterstützung vonposition:fixed
einerseits und Attributselektoren andererseits, weshalb dieses und ähnliche Konstrukte letztlich unzuverlässig sind. Es wird davon ausgegangen, dassposition:fixed
selbst ebenfalls über Attributselektoren versteckt wird (siehe Fehlerhafte Browser-Implementierung: Workaround). -
Das erzwungene Leerzeichen im
a
-Element wird in Browsern, welche die Attributselektoren nicht verstehen, unnötigerweise am Anfang des Überschriftstext angezeigt wird. Daher wird es über eine Regel mit einer Gruppe von Nachkommenselektorenh2 a, h3 a, h4 a
und der Deklarationdisplay:none
versteckt. Diese Regel wird vor der Regel mit den Attributselektoren notiert, sodass der Wert derdisplay
-Eigenschaft durch sie überschrieben wird, sofern der Browser sie versteht.h2 a, h3 a, h4 a { display:none; }
-
Das
a
-Blockelement bewirkt einen Abstand mit der über dieheight
-Eigenschaft angegebenen Höhe zwischen dem Text der Überschrift und dem vor der Überschrift liegenden Absatz. Daher ist es ratsam, den zusätzlichen Außenabstand zwischen demhX
-Element, welches dasa
-Blockelement umfasst, und dem sich darüber befindenden Inhalt auf 0 (Null) zu setzen. Dies wird durch die Deklarationmargin-top:0
erreicht, welche allen relevanten Kapitelüberschriften zugewiesen wird. Des weiteren sollte dashX
-Überschriftelement keinen oberen Innenabstand (padding-top
) besitzen. Dies entspricht zwar in der Regel der Vorgabe, dennoch empfiehlt sich das ausdrückliche Setzen auf Null.Die »Filter«, welche nicht-
position:fixed
-fähige Browser aussortieren sollen, sollten möglich einheitlich sein. Daher wird erneut auf Attributselektoren zurückgegriffen, um die besagten Außenabstände nur in bestimmten Browsern zu deaktivieren. Hier erklärt sich, warum anfangs dasid
-Attribut im Überschriftelement nötig war./* Betrifft Überschriften mit einem
id
-Attribut: */ h2[id], h3[id], h4[id] { margin-top:0; padding-top:0; } -
Auch dem Element, welches vor der Kapitelüberschrift liegt (beispielsweise ein
p
-Absatzelement), sollte ausdrücklich ein unterer Außenabstandmargin-bottom
mit dem Wert 0 zugewiesen werden. Um dies einfach ohne Eingriffe in den Code lösen zu können, kann der Außenabstand allerp
-Elemente sowie anderer vorkommender Blockelemente (zum Beispielol
,ul
,dl
,table
) nach oben verlagert werden (margin-top
).p { margin:1em 0 0 0; }
Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, dem Ankerelement ein blauer Rahmen und ein hellgrauer Hintergrund.
Endergebnis ohne verdeutlichende Rahmen und Hintergründe:
Der beschriebene Code hat zur Folge, dass in allen Browsern, welche die Attributselektoren interpretieren, die CSS-Box des hX
-Überschriftelements durch das a
-Blockelement um dessen Höhe nach oben vergrößert wird. Beim Anspringen des Ankers wird die Oberkante des hX
-Elements an den oberen Rand des Anzeigebereichs gerückt. Dadurch liegt der fest positionierte Bereich über dem leeren Abstand (dem a
-Element), ohne dass der eigentliche Inhalt der Überschrift verdeckt wird.
Mozilla Milestone 16 (Gecko/20000613) Windows und darüber | positiv getestet |
---|---|
Opera 5.12 Windows | Negativ getestet. Das Blockelement wird zwar erzeugt und wie gewünscht angezeigt, aber der Text danach angesprungen. |
Opera 6.00 Windows | Negativ getestet, der Überschriftstext wird angesprungen. |
Opera 6.01 Windows und darüber | positiv getestet |
Safari 1.2 MacOS X | positiv getestet |
Konqueror 3.2.2 Linux | positiv getestet Konqueror zeigt einen angesprungenen Anker jedoch nicht direkt am oberen Rand des Anzeigebereichs an, sondern lässt einen schmalen Spalt dazwischen. Er springt also nicht so weit nach unten wie die anderen Browser, wodurch ein Teil des leeren Abstands zwischen der Unterkante des fest positionierten Bereichs und dem Überschriftstext zu sehen ist. |
Beispiel zur ersten CSS-Lösung
Beispieldokument zur ersten CSS-Lösung
Nachteile der ersten CSS-Lösung
- Durch das zum Blockelement umformatierte
a
-Element entsteht ein ständiger Abstand vor jenen Überschriften, die Anker enthalten. Dieser Abstand muss mindestens die Höhe des fest positionierten Bereiches einnehmen, sodass er mitunter selbst bei abgeschalteten Außenrändern der beteiligten Elemente unverhältnismäßig groß und unansehnlich wird – dies lässt sich nicht verhindern. Gleichwohl liegt dem optischen Einschnitt bestenfalls eine Trennung zwischen zwei thematischen Bereichen zugrunde, wenn die Anker in Kapitel- beziehungsweise Abschnittsüberschriften liegen. Es ist folglich ratsam, den fest positionierten Bereich möglichst schmal zu gestalten, sodass der Abstand vor jeder Überschrift nicht überdimensioniert wirkt. - Infolge der Vergrößerung der Box der Überschriftelemente durch das
a
-Blockelement lassen sich einige CSS-Formatierungen (beispielsweise Hintergrundfarben, Innenabstände, Rahmen) nicht wie gewohnt anwenden, beziehungsweise sie hätten einen anderen Effekt. In diesen Fällen ist eine genaue Abstimmung mit dem Lösungsansatz notwendig. Die Brauchbarkeit der Lösung hängt somit von der Beschaffenheit des Layouts und möglichen Eingeständnissen ab. - Um die Lücke vor Überschriften möglichst klein zu halten, sollten alle Elemente, die direkt vor den Überschriften liegen, keinen unteren Außenabstand haben (
margin-bottom:0
). Normalerweise hat einp
-Element obere und untere (Mindest-)Abstände, die mit den Abständen des vorherigen und folgenden Elements zusammenfallen. Dieses Konzept wird durchbrochen, indem nur noch der obere Außenabstand zählt. Diese Änderung der Raumorganisation muss im gesamten Layout berücksichtigt werden. - Um Attributselektoren als »Filter« verwenden zu können, wird der Anker genau genommen zweimal definiert. Zum einen über das
id
-Attribut beimhX
-Element, zum anderen durch dasa
-Element mitname
-Attribut in diesem Überschriftenelement. Das Attributname
beia
-Elementen sowie dasid
-Attribut teilen sich ein und denselben Namensraum. Das Definieren von<a name="Name"></a>
und die Angabe vonid="Name"
bei einem anderen Element kommt also einer doppelten Deklaration gleich. Dies ist nicht konform zum HTML-4.01-Standard: Anchors with the id attribute. Da die Eigentümlichkeit des gleichen Namensraumes nicht in der DTD von HTML oder XHTML ausgedrückt werden kann, ist der obige Code gültig im Sinne der DTD. Zwar weist der Standard ausdrücklich darauf hin, dass diese Vorgehensweise zu vermeiden ist, aber da sie keinerlei bekannte Nachteile in der Praxis nach sich zieht, kann diese Abweichung vom Standard im Vergleich zu den anderen Nachteilen noch am ehesten hingenommen werden.
Weiterführende Links
Zweite CSS-Lösung: Generierter Inhalt als Blockelement
Die folgende Lösung käme prinzipiell ohne a
-Elemente aus und bräuchte lediglich id
-Attribute. Im Kontext von HTML und XHTML im öffentlichen WWW hat der Verzicht jedoch einen Nachteil. Denn obwohl das id
-Attribut bereits seit HTML 4.0 zum Setzen einer Sprungmarke erlaubt ist, unterstützen dies einige alte Browser nicht. Da das Funktionieren der Hyperlinks wesentlich ist, sollten Anker weiterhin über ein a
-Element mit einem name
-Attribut und im Falle von XHTML 1.0 einem zusätzlichen id
-Attribut gelöst werden. Für in sich abgeschlossene, homogene Umgebungen gilt dies natürlich nicht.
Im Folgenden wird mit einem zusätzlichen a
-Element mit name
-Attribut gearbeitet, welches leer am Anfang (oder Ende) eines Überschriftelements steht. Es wäre zwar logischer, wenn es den Überschrifttext umspannen würde. Allerdings brächte das Probleme mit der Pseudo-Klasse :hover
, welche in der Regel für a:hover
gebraucht wird. a:hover
gilt in neueren Browsern für alle a
-Elemente, ob mit Anker- oder Hyperlink-Funktion. Dies ließe sich mit a:link:hover
bzw. a:visited:hover
umgehen oder über a[name]:hover
kompensieren. Da nicht alle Browser diese Techniken unterstützen, wird hier nur oberflächlich auf sie hingewiesen wird.
-
CSS 2 bietet die Möglichkeit, über die sogenannten Pseudo-Elemente
:before
und:after
in Zusammenspiel mit der Eigenschaftcontent
Inhalte einzufügen, unter anderem Text. Die Idee ist nun, das in der ersten CSS-Lösung zum Blockelement umformatiertena
-Element durch erzeugten Textinhalt am Anfang des Überschriftelements zu ersetzen, welcher ebenfalls als Blockelement formatiert wird. Der Code der Überschrift würde etwa lauten:<h2 id="sprungstelle"><a name="sprungstelle"></a>Beispielüberschrift</h2>
Das Element
<a name="sprungstelle"></a>
ist wie gesagt für die Lösung nicht relevant. -
Um den Text bei Überschriftelementen einzufügen, wird die Selektorgruppe
h2[id]:before, h3[id]:before, h4[id]:before
verwendet. Natürlich ist auch hier eine Ausweitung auf andere Elemente nach demselben Schema möglich. Durch die Typselektorenh2
,h3
beziehungsweiseh4
jeweils kombiniert mit dem Attributselektor[id]
werden nur diejenigen Überschriftelemente ausgewählt, welche einid
-Attribut besitzen und somit als Linkanker dienen. Das Pseudo-Element:before
im jeweiligen Teilselektor bedeutet, dass der erzeugte Inhalt vor dem bestehenden Inhalt (im konkreten Fall Textinhalt) des betreffenden Elements eingefügt wird.Attributselektoren dienen hier wieder als (zweifelhaftes) Filterkriterium, mit welchem die Regel vor nicht-
position:fixed
-fähige Browser versteckt werden soll. Das Pseudoelement:before
könnte ebenfalls als »Filter« wirken. Wenn alle Überschriftelementeid
-Attribute enthielten, erübrigte sich der genannte Attributselektor. Er sollte aber auch dann verwendet werden, um die Hürden einheitlich zu halten, siehe unten. -
In der entstehenden CSS-Regel wird die Eigenschaft
content
verwendet, um den einzufügenden Textinhalt zu definieren. Der Eigenschaftswert muss dazu aus einer mit einfachen oder doppelten Anführungszeichen begrenzten Zeichenkette bestehen. Die Deklarationcontent:'Beispiel '
in einer Regel mit dem genannten Selektor würde im Beispielfalle<h2 id="a">Überschrift</h2>
dazu führen, dass die Überschrift nach Anwendung der Regel den Text »Beispiel Überschrift« enthält. Im Hinblick auf die Aufgabenstellung reicht es aus, ein Leerzeichen anzugeben, alsocontent:' '
. (Offenbar genügt auch eine leere Zeichenkette, das heißtcontent:''
.)Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, der eingefügten Text (
content:'Beispiel '
) ist blau und hat einen hellgrauer Hintergrund. - Das eingefügte Pseudo-Element beziehungsweise dessen Textinhalt bildet eine sogenannte Inline-Box innerhalb der Box des Überschriftelements. Dies entspricht dem zunächst unformatierten
a
-Element in der ersten CSS-Lösung. Über die Deklarationdisplay:block
wird das Pseudo-Element auf die bekannte Art zum Blockelement umformatiert (beziehungsweise die erzeugte Box wird zu einer Block-Box). Es liegt damit oberhalb der Zeilenbox des Überschriftstextes. -
Nun kann dem Pseudo-Element über die Eigenschaft
height
eine feste Höhe zugeordnet werden, damit es als Platzhalter zwischen der oberen Kante des Überschriftelements und dessen Text fungiert. Die Höhe muss mindestens der des fest positionierten Bereiches entsprechen, damit dieser beim Anspringen des Ankers nur das Pseudo-Element überdeckt.Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, der eingefügten Block-Box (
content:' '
) ein blauer Rahmen und ein hellgrauer Hintergrund.Die entstehende CSS-Regel wäre etwa folgendermaßen aufgebaut:
h2[id]:before, h3[id]:before, h4[id]:before { content:' '; display:block; height:60px; /* Dieser Wert kann variieren. */ }
-
Da das eingefügte Pseudo-Element eine breite Lücke vor dem Überschrifttext erzeugt, empfiehlt es sich, wie bei der ersten CSS-Lösung die oberen Außen- und Innenabstände der Überschrift über die Eigenschaften
margin-top
undpadding-top
ausdrücklich auf 0 (Null) zu setzen. Damit die Abstände nur in denjenigen Browsern abgeschaltet werden, welche auch die Block-Box einfügen, wird auf den Attributselektor[id]
als Auswahlkriterium zurückgegriffen, der auch in der Regel Verwendung fand, die den Text einfügt:/* Betrifft Überschriften mit einem
id
-Attribut: */ h2[id], h3[id], h4[id] { margin-top:0; padding-top:0; }Es bleibt wie gesagt fraglich, ob sich Attributselektoren zum zuverlässigen Ausschluss derjenigen Browser eignen, welche weder feste Positionierung über
position:fixed
noch CSS-generierte Inhalte über:before
undcontent:'Text'
unterstützen. -
Mithilfe der beschriebenen Methode werden die unteren Ränder (
margin-bottom
) aller Blockelemente zugunsten der oberen Ränder (margin-top
) deaktiviert, um die entstehende Lücke vor Überschriften zu verkleinern.p { margin:1em 0 0 0; }
Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, der eingefügten Block-Box ein blauer Rahmen und ein hellgrauer Hintergrund.
Endergebnis ohne Hervorhebungen.
Sofern der Browser die genannten Regeln wie gewünscht umsetzt, entstehen vor allen relevanten Überschriften durch die eingefügten Blockinhalte Abstände beziehungsweise Freiräume, die die Boxen der Überschriften nach oben vergrößern. Beim Anspringen einer mit einem Anker versehenen Überschrift wird der Dokumentausschnitt so verschoben, dass sich die obere Kante der Überschriftbox mit der des Anzeigebereichs deckt. Der fest positionierte Bereich überlappt somit den Freiraum. Der Überschrifttext liegt vertikal unterhalb des fest positionierte Bereich und bleibt sichtbar.
Mozilla Milestone 16 (Gecko/20000613) Windows und darüber | positiv getestet |
---|---|
Opera 5.12 bis 6.06 Windows | Negativ getestet. Der Überschriftstext wird gesprungen, nicht die Oberkante der eingefügten Block-Box. |
Opera 7.02 Windows und darüber | positiv getestet |
Safari 1.2 MacOS X und Konqueror 3.2.2 Linux | Negativ getestet. Die Block-Box wird zwar mit der gewünschten Höhe eingefügt, der Überschriftstext erscheint aber nicht unter derselbigen, sondern darin. Dadurch schließt der Überschriftstext direkt an das Element davor mit class="lp" an und zum ersten Absatz des eingeleiteten Abschnitts entsteht eine große Lücke. Beim Anspringen des Ankers liegt der Überschriftstext also unter dem fest positionierten Bereich und ist nicht lesbar. |
Beispiel für die zweite CSS-Lösung
Beispieldokument zur zweiten CSS-Lösung
Nachteile der zweite CSS-Lösung
Dieser Lösungansatz hat dieselben Nachteile wie die erste CSS-Lösung.
[Gibt es weitere/zusätzliche Nachteile?]
Weiterführende Links
Dritte CSS-Lösung: Relativ positionierter Anker
- Wie in der ersten Lösung wird zunächst ein
a
-Element mit Ankerfunktion an den Anfang deshX
-Elements gesetzt, welches lediglich ein geschütztes Leerzeichen enthält. Es ist ebenfalls der erste Kindknoten. Einid
-Attribut für dash2
-Element ist jedoch nicht unbedingt notwendig, es kann im Falle von XHTML 1.0 wie gewohnt dema
-Element vergeben werden.<h2><a name="sprungstelle"> </a>Beispielüberschrift</h2>
- Die beschriebene Selektorgruppe
h2 a[name], h3 a[name], h4 a[name]
wird übernommen, um die Anker mit CSS zu formatieren, die in Überschriften liegen. Die Attributselektoren werden erneut zur (zweifelhaften) Identifizierung vonposition:fixed
-fähigen Browsern verwendet. - Über
display:block
wird das Ankerelement als Blockelement formatiert, sodass es imhX
-Element über der Zeilenbox des Überschriftstext liegt. Dies ist vor allem notwendig, um später die Größe des Elements anzupassen. - Der als Blockelement dargestellte Anker wird mittels
position:relative
und der Eigenschafttop
(oderbottom
) relativ zur ursprünglichen Position im Elementfluss vertikal nach oben verschoben, in diesem Fall mindestens um den Wert der Höhe des fest positionierten Bereiches. Entweder kann der Wert negativiert zusammen mittop
oder als positive Größe zusammen mitbottom
verwendet werden, also beispielsweisetop:-60px
oderbottom:60px
. -
Durch die relative Positionierung liegt die Box des
a
-Elements um die angegebene Höhe oberhalb der Box deshX
-Elements und überlappt die dortigen Boxen, beispielsweise den Textabsatz vor der Überschrift. Da das Element lediglich ein Leerzeichen enthält, ist es zwar nicht sichtbar, wird aber beispielsweise beim Markieren des Textes erkennbar. Die Ausmaße der verschobenen Box können zur Veranschaulichung durch eine Hintergrundfarbe, beispielsweise mittelsbackground-color:black
, sichtbar gemacht werden.Bei einer Verschiebung mittels
position:relative
wird die Elementbox zwar verrückt, der ursprünglich eingenommene Raum an der sogenanntenPosition im normalen Fluss
bleibt reserviert, das heißt, er bleibt leer und transparent. Die Position desa
-Elements im normalen Fluss liegt wie gesagt durch die Deklarationdisplay:block
innerhalb deshX
-Elements oberhalb des darin enthaltenen Textes.Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, dem verschobenen Ankerelement ein blauer Rahmen.
Um diesen Phänomenen zu begegnen, wird dem
a
-Element mittelsheight:0
die Höhe 0 (Null) zugewiesen. Die Breite wird mittelswidth:0
ebenfalls auf 0 gesetzt, dies ist jedoch vernachlässigbar. Die Elementbox nimmt dadurch letztlich keinen Raum ein, weder an der Position, an die sie verschoben wurde, noch an Position im normalen Fluss. In Zweifelsfällen, in denenwidth:0
nicht wirksam ist, kann zusätzlichfont-size:0; line-height:0;
bzw.font-size:2px; line-height:0;
eingesetzt werden, damit die durch das erzwungene Leerzeichen erzeugte Zeilenbox in jedem Fall die minimalste Höhe hat.Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben. Das verschobene Ankerelement nimmt keinen Raum ein und ist ohne Rahmen nicht sichtbar.
-
Trotz einer Höhe und Breite von 0 taucht das
a
-Element beim Markieren des Textes in einigen Browsern auf und wirkt störend. Um es vollends zu verbergen, wirdvisibility:hidden
angegeben.Illustration: Beim Markieren des Texts wird das Ankerelement sichtbar.
Durch
visibility:hidden
wird es vollends versteckt. - Wie bei der ersten CSS-Lösung beschrieben, wird eine zweite CSS-Regel mit gewöhnlichen Nachkommenselektoren vor der Regel mit Attributselektoren notiert, um in nicht
position:fixed
-fähigen Browsern den Anker samt erzwungenem Leerzeichen zu verstecken.
Die entstehenden CSS-Regeln sähen so oder ähnlich aus:
h2 a, h3 a, h4 a { display:none; } h2 a[name], h3 a[name], h4 a[name] { display:block; width:0; height:0; position:relative; top:-60px; /* Dieser Wert kann variieren. */ visibility:hidden; }
Trotz des Umstands, dass das verschobene Ankerelement keinen Raum einnimmt und unsichtbar ist, wirkt es weiterhin als Anker und kann an seiner neuen Position angesprungen werden. Wird also ein Link zum Anker annavigiert oder das Dokument mit einer Ankerangabe geladen, zeigt der Browser das unsichtbare Ankerelement direkt oben im Anzeigebereich an, sofern die Formatierungen richtig umgesetzt werden. Die Überschrift selbst erscheint im Erfolgsfalle an der gewünschten Stelle direkt unterhalb des fest positionierten Bereiches, ohne dass sie verdeckt wird.
Der Vorteil dieser Lösung ist, dass am äußeren Erscheinungsbild des Dokuments selbst nichts geändert wird. Von den vor den Überschriften positionierten Ankerelemente merkt der Benutzer nichts, wenn der Browser die CSS-Formatierungen richtig verarbeitet und anwendet. Der große Nachteil des ersten Lösungsansatzes – der durch das a
-Blockelement erzwungene Abstand vor jeder Überschrift – ist bei dieser Lösungsmöglichkeit nicht vorhanden.
Mozilla 0.9.9 Windows | negativ getestet. |
---|---|
Mozilla 1.0 Release Candidate 1 Windows und darüber | positiv getestet |
Opera 6.00 Windows | negativ getestet. |
Opera 6.01 Windows und darüber | positiv getestet |
Safari 1.2 MacOS X | negativ getestet. |
Konqueror 3.2.2 | positiv getestet, allerdings tritt das aus den vorigen Lösungen bekannte Phänomen auf: Das Überschriftselement schließt beim Anspringen nicht direkt an die Oberkante des Anzeigebereichs an, somit liegt der Überschriftstext um ungefähr 20 Pixel weiter unten als in den anderen Browsern. |
In Mozilla M16 funktioniert die Lösung (mit font-size:2px; line-height:0;
, siehe unten), wenn der Anker aus einem externen Dokument angesprungen wird oder in die Adresszeile eingegeben wird, während ein fremdes Dokument angezeigt wird. Im Mozilla M18 bis einschließlich 0.9.9 funktioniert die Lösung nicht wie gewünscht. Im Mozilla 1.0 RC1 und darüber funktioniert die Lösung nur, wenn der fragliche Anker über einen dokumentinternen Link angesprungen wird.
Im Opera 7.02, 7.11, 7.21, 7.23 und 7.5 wirkt die Formatierung height:0
nur im standardkonformen Rendermodus, der abhängig von der Dokumenttyp-Angabe ist. Im Quirks-Modus hat der relativ positionierte Anker eine bestimmte Höhe, wodurch vor der Oberkante der Überschrift und dem Überschriftstext freier Platz bleibt. Auch Mozilla zeigt dieses Verhalten bis auschließlich Version 1.3, und zwar unabhängig vom Rendermodus. Dies lässt sich durch font-size:2px; line-height:0
kompensieren. Im Opera (getestet mit 6.01 bis 7.5) führt dies jedoch zu einer Verschiebung nach oben beim Anspringen des Ankers. Dies hängt damit zusammen, welche Mindestschriftgröße eingestellt ist.
Keine Mindestschriftgröße | 13 Pixel Mindestschriftgröße |
---|---|
![]() | ![]() |
Der Wert 2px für font-size
bietet sich an, weil Mozilla bei 0 das Element als nicht existent ansieht und die Lösung insgesamt nicht mehr wirkt. In Mozilla-Versionen unter 1.3, in denen wie gesagt height:0
nicht wirkt, setzt eine gegebenenfalls eingestellte Mindestschriftgröße die Angaben font-size:2px; line-height:0
außer Kraft.
Keine Mindestschriftgröße | 13 Pixel Mindestschriftgröße |
---|---|
![]() | ![]() |
Unglücklicherweise lassen sich nicht beide Fehler gleichzeitig beheben. Denn wenn Opera 7.x im standardkonformen Modus bedient wird, damit height:0
wirkt, und für Mozilla < 1.3 font-size:2px; line-height:0
eingesetzt wird, hat die Mindestschriftgröße im Opera die besagte Auswirkung auf die Höhe des relativ positionierten Ankerelements. Immerhin ist die durch die Mindestschriftgröße erzeugte Lücke höchstwahrscheinlich schmaler als diejenige, die entsteht, wenn keine wirksame Höhenbegrenzung angegeben ist.
Beispiel zur dritten CSS-Lösung
Beispieldokument zur dritten CSS-Lösung
Weiterführende Links
Vierte CSS-Lösung: Negativer Außenabstand und kompensierender Innenabstand
- Wie auch die dritte Lösung käme auch die folgende ohne
a
-Elemente mitname
-Attributen aus. Aus den beschriebenen Gründen wird dennoch zur Abwärtskompatibilität bei jedem Anker zusätzlich ein leeresa
-Ankerelement eingefügt, welches später gegebenenfalls entfernt werden kann. Das Codeschema einer Überschrift wird übernommen:<h2 id="sprungstelle"><a name="sprungstelle"></a>Beispielüberschrift</h2>
-
Die CSS-Eigenschaft
margin
kann negative Werte annehmen. Angewandt auf ein normal fließendes Element führt ein negativer oberer Außenabstand (margin-top
) dazu, dass die Elementbox vertikal nach oben verschoben wird und die dort befindlichen Boxen überlappt. Alle folgenden Boxen im normalen Fluss rücken entsprechend auf, sie schließen weiterhin (unter Berücksichtigung der Außenabstände) an die Unterkante der verschobenen Box an.Illustration: Eine Überschrift erhält einen negativen oberen Außenabstand. Sie hat zur Verdeutlichung einen roten Rahmen. Die Boxen der beiden Absätze vor der Überschrift werden überlappt, die Boxen der beiden Absätze nach der Überschrift rücken entsprechend nach oben.
Der Clou ist nun, die Überschriften mit den Ankern zunächst mit einem negativen
margin-top
zu versehen und gleichzeitig einen oberen Innenabstandpadding-top
mit demselben Wert zu vergeben. Dieser Wert muss mindestens so groß sein wie der fest positionierte Bereich. Dadurch erscheint der Inhalt der Überschrift, beispielsweise der Text, letztlich an der Ausgangsposition, als wäre kein negativermargin-top
vergeben worden.Illustration: Die Überschrift erhält einen negativen
margin-top
und einen positiven gleich großenpadding-top
. Sie hat zur Verdeutlichung einen roten Rahmen. Gäbe es diesen Rahmen nicht, ließe sich das Beispiel nicht von einer Version ohne diese Formatierungen unterscheiden. - Die besagten Deklarationen werden in einer Regel mit dem Selektor
h2[id], h3[id], h4[id]
untergebracht. Betroffen sind dannh2
-,h3
- undh4
-Elemente mit einemid
-Attribut. Die Attributselektoren wirken gleichzeitig als (letztlich unzuverlässiger) Filter, um nicht-position:fixed
-fähige Browser davon abzuhalten, die Regel anzuwenden.
Die entstehende CSS-Regel sähe etwa wie folgt aus:
h2[id], h3[id], h4[id] { margin-top:-60px; /* Dieser Wert kann variieren. */ padding-top:60px; /* Dieser Wert kann variieren. */ }
Opera 5.12 bis 6.06 Windows | negativ getestet |
---|---|
Opera 7.02 Windows und darüber | positiv getestet |
Mozilla M16 Windows | positiv getestet, allerdings nur beim Anspringen des Ankers über einen dokumentinternen Link. Beim Anspringen des Ankers über einen externen Link oder beim Eingeben der Adresse in die Adresszeile liegt die Überschrift unter dem fest positionierten Bereich. |
Mozilla M18 Windows | positiv getestet, allerdings nur beim Anspringen des Ankers über einen dokumentinternen Link. Mozilla M18 springt aus einem fremden Dokument referenzierten Anker anscheinend gar nicht an, dasselbe gilt für das Eingeben der Adresse in die Adresszeile, während ein anderes Dokument gezeigt wird. |
spätestens ab Mozilla 0.9 Windows | positiv getestet (ohne Einschränkungen) |
Safari 1.2 MacOS X | positiv getestet |
Konqueror 3.2.2 Linux | positiv getestet, allerdings tritt das aus den vorigen Lösungen bekannte Phänomen auf: Das Überschriftselement schließt beim Anspringen nicht direkt an die Oberkante des Anzeigebereichs an, somit liegt der Überschriftstext um ungefähr 20 Pixel weiter unten als in den anderen Browsern. |
Beispiel für die vierte CSS-Lösung
Beispieldokument zur vierten CSS-Lösung
Nachteile der vierten CSS-Lösung
Insgesamt hat diese Lösungsmöglichkeit durch die Überlappung der Boxen ein großes Problempotenzial. Zum einen können die CSS-Eigenschaften margin
, padding
, border
und background
nicht wie gewohnt auf die betroffenen Überschriftelemente angewendet werden. Dazu könnten beispielsweise div
-Elemente eingefügt werden, welche die Elemente zwischen zwei anspringbaren Überschriften gruppieren und statt den Überschriften nach oben verschoben werden und padding-top
erhalten, um zur Ursprungsposition zurückzukehren. Zum anderen werden alle möglichen Mausereignisse durcheinandergebracht. Das betrifft sowohl die CSS-Pseudoklasse :hover
als auch die entsprechenden JavaScript/DOM-Events click
, mouseover
, mouseout
.
Fünfte CSS-Lösung: Anker mit oberem Innenabstand
<h2 id="sprungstelle"><a name="sprungstelle"> </a>Beispielüberschrift</h2>
h2 a[name] { padding-top:60px; /* Dieser Wert kann variieren. */ width:0; font-size:0; }
[Erklärung fehlt]
Opera 5.12 bis 6.06 Windows | negativ getestet |
---|---|
Opera 7.02 Windows und darüber | positiv getestet |
Mozilla M16 Windows | positiv getestet, allerdings nur beim Anspringen des Ankers über einen externen Link und beim ersten Eingeben der Adresse zum Anker in die Adresszeile, das heißt während ein fremdes Dokument angezeigt wird |
Mozilla M18 bis einschließlich 0.9.9 Windows | negativ getestet |
Mozilla 1.0 Release Candidate 1 Windows und darüber | positiv getestet, allerdings nur beim Anspringen des Ankers über einen dokumentinternen Link. Beim Anspringen des Ankers über einen externen Link oder beim Eingeben der Adresse zum Anker in die Adresszeile liegt die Überschrift unter dem fest positionierten Bereich. |
Safari 1.2 MacOS X und Konqueror 3.2.2 Linux | negativ getestet |
Beispiel für die fünfte CSS-Lösung
Beispieldokument zur fünften CSS-Lösung
JavaScript-Teillösung: Wenn das Dokument mit einem Anker aufgerufen wird
Die JavaScript-Objektmethode window.scrollBy()
bietet die Möglichkeit, den Fokus des Fensters um eine angegebene Anzahl von Pixeln zu verschieben. Es liegt nahe, diese Methode zu verwenden, um einen unter dem fest positionierten Bereich liegenden Anker wieder ins Blickfeld zu holen, indem der sichtbare Dokumentausschnitt nachträglich um die Höhe des fest positionierten Bereiches nach oben verschoben wird.
Über die Objekteigenschaft location.hash
wird abgefragt, ob das aktuelle Dokument mit einem Anker aufgerufen wurde. Falls dies zutrifft, wird der Browser über scrollBy()
angewiesen, den Dokumentausschnitt vertikal um die Höhe des fest positionierten Bereiche nach oben (deshalb der negative Wert) zu blättern:
if (location.hash) window.scrollBy(0, -60);
Dieser Code sollte nach dem vollständigen Laden des Dokuments ausgeführt werden. Dazu wird der Event load
bzw. Event-Handler onload
genutzt. Der Code wird in einer Funktion untergebracht, die den load
-Event verarbeitet:
function scrollup () { if (location.hash) window.scrollBy(0, -60); } window.onload = scrollup;
Diese Umsetzung ist eine von verschiedenen Möglichkeit. Wenn noch weitere Aktionen beim Eintreten des load
-Ereignisses gestartet werden sollen, wird eine Helferfunktion benötigt, die mehrere Handler-Funktionen zulässt, ohne vorher registrierte zu überschreiben. Eine solche Helferfunktion kommt bei der Kombinationslösung zum Einsatz (siehe auch addEvent() auf quirksmode.org).
Weiterführende Links
JavaScript-Teillösung: Wenn ein dokumentinterner Anker angesprungen wird
Wenn ein Hyperlink auf einen dokumentinternen Anker verweist, kann man ebenfalls die Methode scrollBy()
verwenden. Damit lässt sich der sichtbare Seitenausschnitt nach oben verschieben, um den Anker und den zugehörigen Absatz oder die Kapitelüberschrift unter dem fest positionierten Bereich hervorzuholen. scrollBy()
muss in diesem Fall nach dem Anspringen des Linkziels (des Ankers) ausgeführt werden.
Da ein Hyperlink meistens durch einen Mausklick aktiviert wird, verwendet man den Event-Handler onclick
, um JavaScript-Code anzugeben, der beim Anklicken des Links ausgeführt werden soll. Glücklicherweise tritt das click
-Ereignis in viele Browsern auch dann ein, wenn der Link durch die Tastatur aktiviert wird. Somit kann die Korrektur des Seitenausschnitts auch dann vorgenommen werden, wenn das Dokument nicht mit einer Maus bedient wird.
Alle dokumentinternen Hyperlinks benötigen also einen Handler für das click
Ereignis, der den Seitenausschnitt um eine bestimmte Anzahl von Pixeln nach oben blättert. Das Anspringen des Ankers und das Eintreten des click
-Ereignisses passieren allerdings nahezu gleichzeitig. Deshalb ist unbekannt, ob scrollBy()
vor oder nach dem Anspringen des Ankers ausgeführt wird. Die streng chronologische Abfolge dieser beiden Ereignisse ist nicht gesichert, jedoch zwingend notwendig für die Korrektur des sichtbaren Seitenausschnittes.
Untersuchungen zeigen, dass ein einfacher scrollBy()
-Aufruf in <a href="#sprungstelle" onclick="window.scrollBy(0, -60);">Beispiellink zum Anker</a>
meist bereits vor dem Anspringen des Zielankers ausgeführt wird. Dies lässt sich umgehen, indem man scrollBy()
mittels setTimeout()
verzögert aufruft, damit scrollBy()
möglichst nach dem Anspringen des Ankers ausgeführt wird, um die Sicht auf den Anker wieder freizugeben.
Unglücklicherweise ist die benötigte Verzögerungszeit nicht bekannt. Die Dauer des Anspringens hängt von unzähligen Faktoren ab, sie unterscheidet sich von Rechner zu Rechner, von Browser zu Browser und von Dokument zu Dokument. Falls ein zu kleiner Verzögerungswert gewählt wird, besteht die Gefahr, dass das Blättern schon vor dem Anspringen stattfinden. Falls eine zu lange Verzögerung angegeben wird, könnte der Bezug zur Eingabe (Klick auf den Link) verloren gehen und der Benutzer könnte die plötzliche Seitenbewegung als störend empfinden. Eine Verzögerungszeit zwischen 100 und 500 Millisekunden scheint ein annehmbarer Mittelweg zu sein.
Angenommen, es wird eine Verzögerungsdauer von 100 Millisekunden gewählt, so sähe der setTimeout()
-Aufruf wie folgt aus:
window.setTimeout('window.scrollBy(0, -60)', 100);
Eine einfache Möglichkeit, diesen Code beim Anklicken von dokumentinternen Link auszuführen, ist das Auslagern in eine zentrale Funktion im Dokumentkopf (head
-Element) sowie das Attribut onclick="scrollup()"
bei jedem Link, der in Betracht kommt:
function link_scrollup () { window.setTimeout('window.scrollBy(0, -60)', 100); }
<a href="#sprungstelle" onclick="scrollup()">Beispiellink zum Anker</a>
Dieses Beispiel wurde erfolgreich getestet mit den position:fixed
-fähigen Browsern Opera ab Version 5.12, Mozilla ab M16 und Safari ab 1.2. Mit vorigen Versionen wurde nicht getestet. Der Code wirkt ebenfalls auf nicht-position:fixed
-fähige Browser, da die verwendeten Methoden setTimeout()
und scrollBy()
bereits ab Netscape 4 und Internet Explorer 4 verfügbar sind.
JavaScript-Kombinationslösung: Automatisches Vergeben der Event-Handler
Es ist freilich umständlich, allen dokumentinternen Links von Hand ein onclick
-Attribut zu geben. Es besteht stattdessen die Möglichkeit, diesen Hyperlinks die Event-Handler automatisch beim Laden des Dokuments durch eine zentrale JavaScript-Routine zuzuweisen. Dazu wird der Code, der beim Öffnen eines Dokuments mit einem Anker die scrollBy()
-Korrektur vornimmt, zusammen mit den Anweisungen, die die Korrektur beim Anspringen dokumentinterner Anker vornehmen, in einer gemeinsamen Funktion untergebracht.
In einem script
-Element im Dokumentkopf oder in einer externen JavaScript-Datei werden die nötigen Funktionen addEvent()
, link_scrollup()
und init_position_fixed()
deklariert. Die Werte -60 und 100 sind variabel. 60 stellt hier beispielhaft die Höhe des fest positionierten Bereichs dar, 100 ist die Verzögerungsdauer in Millisekunden bis zur Fokuskorrektur.
/* Allgemeine Funktion zum Hinzufügen von Handlerfunktionen für ein bestimmtes Ereignis an einem bestimmten Objekt */ function addEvent (obj, event, func) { if (typeof(obj["on" + event]) == "function") { var old_func = obj["on" + event]; obj["on" + event] = function (e) { if (!e) e = window.event; var return1, return2; return1 = old_func(e); return2 = func(e); if (return1 === false || return2 === false) return false; }; } else { obj["on" + event] = func; } } /* link_scrollup() korrigiert den Fokus beim Klicken eines Links zu einem dokumentinternen Anker */ function link_scrollup () { window.setTimeout('window.scrollBy(0, -60)', 100); } /* init_position_fixed() wird beim Laden des Dokuments ausgeführt */ function init_position_fixed () { var i, hyperlinks, linkcount, link_url, document_url, hash_position, pattern; /* Hier wäre Platz für einen Algorithmus, welcher bestimmt, ob der Browserposition:fixed
versteht, und welcher die Funktion vorzeitig abbricht, falls dies nicht der Fall ist. */ /* Mindestens muss JavaScript 1.2 unterstützt werden.setTimeout()
gehört zu JavaScript 1.0 und muss nicht abgefragt werden. Beende die Funktion, wennscrollBy()
nicht zur Verfügung steht. */ if (!window.scrollBy) return true; /* Korrigiere Fokus beim Laden des Dokuments */ if (location.hash) window.scrollBy(0, -60); /* Weise allen Links zu dokumentinternen Ankern den Event-Handler zu */ hyperlinks = document.links; linkcount = hyperlinks.length; /* Konstruiere die Adresse des aktuellen Dokuments ohne Anker */ document_url = location.href; hash_position = document_url.indexOf("#"); if (hash_position > -1) { /* Falls die aktuelle Adresse einen Anker enthält, schneide ihn ab */ document_url = document_url.substring(0, hash_position); } /* Falls es sich um ein lokales Dokument handelt (file
-Protokoll), vereinheitliche die Schreibweise */ pattern = new RegExp("file:///"); document_url = document_url.replace(pattern, "file://localhost/"); /* Durchlaufe alle Hyperlinks des Dokuments */ for (i = 0; i < linkcount; i++) { /* Gehe zum nächsten Link über, falls diehash
-Eigenschaft leer ist oder nur#
enthält */ if (!hyperlinks[i].hash || hyperlinks[i].hash == "#") continue; /* Konstruiere die Zieladresse des Links ohne Anker */ link_url = hyperlinks[i].href; hash_position = link_url.indexOf("#"); /* Falls die Zieladresse einen Anker enthält, schneide ihn ab */ if (hash_position > -1) { link_url = link_url.substring(0, hash_position); } /* Vereinheitlichung, falls es sich um einen Link auf ein lokales Dokument handelt */ if (link_url.indexOf("file://") == 0) { link_url = link_url.replace(pattern, "file://localhost/"); } /* Vergleiche das Linkziel mit der aktuellen Adresse */ if (link_url == document_url) { /* Der Link führt zum aktuellen Dokument und hat einen Anker, damit sind alle Bedingungen erfüllt, vergebe den Event-Handler */ addEvent(hyperlinks[i], "click", link_scrollup); } } /* Starte die Funktioninit_position_fixed()
beim Laden des Dokuments */ addEvent(window, "load", init_position_fixed);
Quelltext ohne Kommentare herunterladen
Falls andere Handler-Funktionen beim Laden des Dokuments ausgeführt werden sollen, sollten diese gleichermaßen über addEvent()
hinzugefügt werden.
Funktionsweise des Scripts
Ein Link zu einem dokumentinterner Anker ist daran erkennbar, dass der href
-Attributwert mit einem #
beginnt und dahinter ein Ankername folgt. Um alle Eventualitäten zu berücksichtigen, wird zudem angenommen, dass ein dokumentinterner Anker auch zusätzlich die Adresse des aktuellen Dokuments enthalten kann. Der erste Fall ließe sich einfach überprüfen, wenn es in JavaScript möglich wäre, den direkten href
-Attributwert in Erfahrung zu bringen. Dieser lässt sich jedoch nicht in allen relevanten Browsern mit getAttribute("href")
unverfälscht auslesen. In anderen Browsern hängt immer die URL des aktuellen Dokuments vorne dran, obwohl im Attribut selbst nur #ankername
steht.
Angenommen, das Dokument wird mit einem Anker aufgerufen und testlink
steht für einen Hyperlink im Dokument mit id="testlink"
, welcher über den Array document.links
oder über document.getElementById("testlink")
angesprochen wird, so lassen sich folgende Werte beobachten:
Browser | location | location.href | location.hash | testlink | testlink.href | testlink.hash | testlink.getAttribute('href') |
---|---|---|---|---|---|---|---|
Netscape 4.51, 4.8 | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL]#testlink | [volle URL]#testlink | #testlink | n/a |
Opera 5.12, 6.0, 6.01, 6.06 | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL] | [volle URL] | #testlink | [volle URL] |
Opera 7.02, 7.11, 7.23 | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL] | [volle URL]#testlink | #testlink | [volle URL]#testlink |
Internet Explorer 5.0, 5.5, 6.0, Opera 7.50, 8.0 | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL]#testlink |
Gecko, Konqueror, Safari | [volle URL]#testlink | [volle URL]#testlink | #testlink | [volle URL]#testlink | [volle URL]#testlink | #testlink | #testlink |
Die Überprüfung wäre einfach, wenn A. der Zielankername des Links, B. die Zieladresse des Links ohne Anker und C. die Adresse des aktuellen Dokuments ohne Anker, sowie einzeln zur Verfügung stünden. Unglücklicherweise zeigt die obige Tabelle, dass nur A. direkt in Erfahrung gebracht werden kann, nämlich über die Eigenschaft hash
.
Die Theorie lautet: Ein Link führt genau dann zu einem dokumentinternen Anker, wenn der Anker des Links (A) gefüllt ist sowie die Zieladresse des Links ohne Anker (B) und Adresse des aktuellen Dokuments ohne Anker (C) übereinstimmen. Die Funktion init_position_fixed()
prüft beim Laden des Dokuments jeden Link auf diese beiden Kriterien und registriert im Erfolgsfalle die Funktion link_scrollup
als Handler des click
-Ereignisses.
Anfangs wird die Adresse des aktuellen Dokuments ohne Anker (C) in Erfahrung gebracht. Die Eigenschaften location
und location.href
enthalten leider den Anker am Ende, wenn das Dokument mit einem Anker aufgerufen wurde. Daher muss dieser gegebenfalls abgeschnitten werden. Am Anfang der for
-Schleife wird geprüft, ob der aktuelle Link einen Anker enthält (A). Anhand dessen wird entschieden, ob der Link weiter untersucht wird. Daraufhin muss die volle Zieladresse des Links ohne Anker (B) herausgefunden werden. Dazu wird die Eigenschaft href
genutzt. Falls sie den Anker am Ende enthält, wird dieser abgeschnitten, ansonsten wird sie direkt übernommen.
Nachdem diese Schritte ausgeführt sind, stehen die nötigen Parameter zur Verfügung. Im Code entspricht B. der Variable link_url
und C. der Variable document_url
. Am Ende wird überprüft, ob diese identisch sind. Falls dies zutrifft, wird die Handler-Funktion registriert.
Beispiel zur JavaScript-Kombinationslösung
Beispieldokument zur JavaScript-Kombinationslösung
Weiterführende Links
Alternative JavaScript-Kombinationslösung
Das automatische Registrieren von Event-Handlern bei allen dokumentinternen Ankern ist vom DOM-Events-Standpunkt vergleichsweise umständlich. Denn das Aktivieren eines Links löst ein click
-Ereignis aus, dass vom obersten Dokument-Objekt document
über das Wurzelelement html
herunter in der Elementhierarchie zum Zielelement wandert (capturing phase). Das Ereignis erreicht das a
-Zielelement, bei dem das Ereignis ursprünglich passierte (target phase). Danach wandert es wieder herauf zum Dokument-Objekt (bubbling phase). Auf diesem Weg herunter und herauf werden alle Handler-Funktionen, die für das click
-Ereignis registriert sind, aufgerufen. Das Aktivieren des Links löst somit ein identisches click
-Ereignis beim document
-Objekt aus.
Es reicht somit aus, nur eine Handler-Funktion beim document
-Objekt zu registrieren, die zunächst einmal alle click
-Ereignisse verarbeitet, die irgendwo im Dokument passieren. Sie kann die scrollBy()
-Korrektur vornehmen, wenn es sich beim Ziel bzw. Ursprung des Ereignisses um einen dokumentinternen Anker handelt. Dazu greift sie über die Eigenschaft target
des Ereignisobjekts auf das Zielelement zu und überprüft, ob es sich um ein a
-Element mit einem href
-Attribut handelt. Wenn dies der Fall ist, wird auf die bereits beschriebene Weise in Erfahrung gebracht, ob der Link zu einem Anker im aktuellen Dokument führt. Schließlich wird im positiven Falle die scrollBy()
-Korrektur vorgenommen.
function handle_internal_link (e) { var hashposition, link_url; var target = e.target; if (typeof(target.nodeType) == "undefined" || typeof(target.nodeName) == "undefined") { return true; } if (target.nodeType == 3) { target = target.parentNode; } if (target.nodeName.toLowerCase() != "a" || target.href.length == 0 || typeof(target.hash) == "undefined" || target.hash.length == 0 || target.hash == "#") { return true; } link_url = target.href; hashposition = link_url.indexOf("#"); if (hashposition > -1) { link_url = link_url.substring(0, hashposition); } if (link_url == document_url) { window.setTimeout('window.scrollBy(0, -60)', 100); } } function init_position_fixed () { if (location.hash) window.scrollBy(0, -60); document_url = location.href; var hashposition = document_url.indexOf("#"); if (hashposition > -1) { document_url = document_url.substring(0, hashposition); } document.addEventListener("click", handle_internal_link, false); } if (window.addEventListener && document.addEventListener) window.addEventListener("load", init_position_fixed, false);
Der Beispielcode nutzt konsequent DOM Events. Mit addEventListener()
werden die Handler-Funktionen für das load
- und das click
-Ereignis in der Bubbling-Phase registriert. Damit wird der Internet Explorer als nicht-position:fixed
-fähiger Browser ausgeschlossen, aber auch ältere Opera-Versionen mit position:fixed
-Unterstützung.
Gemäß DOM 3 Events passiert das load
-Ereignis am document
-Objekt. document.addEventListener("load", ...)
versteht aber nur Opera ab Version 7, Konqueror ab 3.1 und Safari mindestens ab 1.2. Gecko kennt zwar seit langem document.addEventListener()
, es passiert aber kein load
-Ereignis am document
-Objekt. Verbreiteter ist das nicht ganz standardkonforme window.addEventListener("load", ...)
, eine wilde Mischung von DOM Events und dem Netscape-JavaScript-Vermächtnis window.onload
. Dies verstehen Opera ab Version 8, Gecko, Safari mindestens ab 1.2 und Konqueror mindestens ab 3.4. Siehe meinen Beitrag im SELFHTML-Forum zur Thematik. Keine dieser Methoden erreicht alle bekannten position:fixed
-fähigen Browser zuverlässig, beispielsweise Opera vor Version 7 bleibt komplett außen vor.
Beispiel zur alternativen JavaScript-Kombinationslösung
Beispieldokument zur alternativen JavaScript-Kombinationslösung
Weiterführende Links
- W3C: Document Object Model Events (englischsprachig)
- Quirksmode.org: W3C DOM Events (englischsprachig)
- Quirksmode.org: Javascript – Advanced event registration models (englischsprachig)
- JavaScript-Objektreferenz: Eigenschaft
nodeName
- JavaScript-Objektreferenz: Eigenschaft
nodeType
- JavaScript-Objektreferenz: Eigenschaft
parentNode
Nachteile der JavaScript-Lösungen
Zum einen funktionieren die genannten Lösungen nur mit verfügbaren und aktiviertem JavaScript-Interpreter. Zum anderen ist wie bereits angesprochen der Grundstein des Lösungsansatzes selbst unzuverlässig: Das Hochblättern mittels scrollBy()
. Denn die Zeit, die ein Browser benötigt, um einen Anker anzuspringen, ist nicht bekannt und nicht feststellbar. Die eventuell nötige Zeitverzögerung mit setTimeout()
kann folglich nicht für alle Fälle passend angegeben werden. Es ist möglich, dass das Script den sichtbaren Dokumentausschnitt vor dem Anspringen des Ankers verschiebt oder erst viel später. Der Leser könnte es als unangenehm empfinden, wenn geblättert wird, ohne dass eine Verbindung zu einer zeitnahen Eingabe erkennbar ist. Ebenso ist bei der Verwendung von relativen Größenangaben unbekannt, welche Höhe das fest positionierte Element einnimmt. Dadurch ist auch der zweite Parameter, welcher die Anzahl der Pixel angibt, um welche vertikal gescrollt werden soll, nicht eindeutig festlegbar beziehungsweise könnte höchstens aus der offsetHeight
des fest positionierten Bereiches geschlossen werden.
Weiterhin berücksichtigen die JavaScript-Lösungen zwar, dass noch andere Handler-Funktionen für das click
-Ereignis bei den dokumentinternen Links registriert sein können. Diese diese Funktionen müssen damit rechnen, dass unmittelbar vorher bzw. nachher die scrollBy()
-Korrektur durchgeführt wird. Dies schränkt die Möglichkeiten der Ereignis-Verarbeitung potenziell ein. Es ist allerdings möglich, dass die Funktionen die scrollBy()
-Korrektur im Einzelfall verhindern können:
Bei der ersten Kombinationslösung hängt es von der Reihenfolge der Registrierung ab, ob link_scrollup()
vor oder nach den restlichen Handler-Funktionen ausgeführt wird. addEvent(window, "load", init_position_fixed);
sollte erst aufgerufen werden, wenn mittels addEvent()
alle anderen potenziellen click
-Handler für dokumentinterne Links registriert wurden. link_scrollup()
wird auf diese Weise immer als letzte Handler-Funktion aufgerufen wird. Dadurch können die anderen Funktionen im Zweifelsfall das Aufsteigen des Ereignisses im DOM-Knotenbaum mit Ereignisobjekt.stopPropagation()
(bzw. Ereignisobjekt.cancelBubble = true
für den Internet Explorer) abbrechen. Mit return false
oder Ereignisobjekt.preventDefault()
(bzw. Ereignisobjekt.returnValue = false
für den IE) kann zudem die Standardaktion unterbunden werden, das heißt das Annavigieren des Linkziels.
Bei der alternativen Kombinationslösung wird der click
-Handler am document
-Objekt für die Bubbling-Phase registriert. Die restlichen registrierten click
-Handler werden somit vor handle_internal_link()
ausgeführt. Unter diesen Voraussetzungen lässt sich mit Ereignisobjekt.stopPropagation()
das Aufsteigen des click
-Ereignisses zum document
-Element verhindern und mit Ereignisobjekt.preventDefault()
das Anspringen des Ankers.
Ein weitaus größeres Problem stellt der Umstand dar, dass JavaScript-Lösungen grundsätzlich in allen Browsern wirken, die die verwendeten JavaScript-Eigenschaften und -Methoden unterstützen. Das korrigierende Verschieben des sichtbaren Dokumentausschnittes beim Aufrufen des Dokuments mit einem Anker sowie beim Aktivieren eines dokumentinternen Ankers sollte freilich nur vorgenommen werden, wenn der Browser feste CSS-Positierung über position:fixed
unterstützt und somit erst das ursprüngliche Problem auftritt. Aus diesem Grund ist eine Fallunterscheidung notwendig, durch welche nur in denjenigen Browser scrollBy()
-Korrektur vorgenommen wird, die bekanntermaßen position:fixed
hinreichend verstehen. Es ist jedoch keine Möglichkeit bekannt, dies direkt und zuverlässig mittels JavaScript zu überprüfen. Somit sind nur ungenaue Browserweichen möglich, die von bestimmten Unterscheidungsmerkmalen darauf schließen, ob der Browser feste Positionierung mittels position:fixed
unterstützt.
Die erste Kombinationslösung hat in der vorgestellten Fassung keine solche Weiche, die alternative Kombinationslösung setzt DOM Events voraus – mit position:fixed
-Unterstützung hat dies aber nichts zu tun. Es gibt vielfältige Möglichkeiten der Browsererkennung durch JavaScript. Mit der Abfrage der Objekteigenschaft document.defaultCharset
, die nur der Internet Explorer kennt, ließe sich die scrollBy()
-Korrektur zumindest vor dem wichtigsten nicht-position:fixed
-fähigen Browser verstecken. Ein grundlegender Nachteil solcher Browserweichen ist die Unzuverlässigkeit und die mangelnde Zukunftssicherheit. Denn jederzeit könnte ein Browser auftauchen, der zwar gemäß einem solchen Kriterium als ein Browser ohne position:fixed
-Unterstützung identifiziert wird, er es aber durchaus unterstützt.
Postskriptum
An der Entwicklung der Lösungsmöglichkeiten wirkten Roland Skop, Roland Blüthgen und Kristof Lipfert maßgeblich mit. Vielen Dank an die drei.
Letzte Änderung: 2005-12-04 — Version 2.9