molily Navigation

Probleme mit Ankern und position:fixed (feste CSS-Positionierung)

Wie verhindere ich, dass fest positionierte Bereiche Linkanker überdecken?

Anmerkung: Einige Teile dieses Artikel sind momentan noch unfertig, die Lücken sind jeweils gekennzeichnet.

  1. Hinweise zum Arbeiten mit diesem Dokument
  2. Problemstellung
  3. Erste CSS-Lösung: Anker als Blockelement
  4. Zweite CSS-Lösung: Generierter Inhalt als Blockelement
  5. Dritte CSS-Lösung: Relativ positionierter Anker
  6. Vierte CSS-Lösung: Negativer Außenabstand und kompensierender Innenabstand
  7. Fünfte CSS-Lösung: Anker mit oberem Innenabstand
  8. JavaScript-Teillösung: Wenn das Dokument mit einem Anker aufgerufen wird
  9. JavaScript-Teillösung: Wenn ein dokumentinterner Anker angesprungen wird
  10. JavaScript-Kombinationslösung: Automatisches Vergeben der Event-Handler
  11. Alternative JavaScript-Kombinationslösung
  12. Nachteile der JavaScript-Lösung
  13. 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.

  1. Das Überschriftelement selbst erhält ein id-Attribut mit dem Ankernamen als Attributwert.
  2. Ein a-Element mit einem name-Attribut, welches den Anker darstellt, wird an den Anfang einer Kapitelüberschrift gestellt. Das heißt, es folgt direkt der Startmarke des hX-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ötige id-Attribut erhalten. Das id-Attribut wird stattdessen im Überschriftelement untergebracht, um die Überschrift mit einen Attributselektor adressieren zu können, wie sich später zeigen werden.

  3. 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">&nbsp;</a>Beispielüberschrift</h2>
  4. Über eine Gruppe von Attributselektoren wie h2 a[name], h3 a[name], h4 a[name] wird allen a-Elementen, welche innerhalb von h2-, h3- oder h4-Elementen liegen und ein name-Attribut besitzen, folgende CSS-Eigenschaften zugewiesen:

    display:block;
    Über die CSS-Eigenschaft display wird das a-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 Layout px angegeben werden, genauso können aber auch relative Einheiten wie em 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- und h6-Elementen sowie beliebigen anderen ausweiten.

    /* Betrifft a-Elemente innerhalb von hX-Elementen mit name-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 sie position: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 von position:fixed einerseits und Attributselektoren andererseits, weshalb dieses und ähnliche Konstrukte letztlich unzuverlässig sind. Es wird davon ausgegangen, dass position:fixed selbst ebenfalls über Attributselektoren versteckt wird (siehe Fehlerhafte Browser-Implementierung: Workaround).

  5. 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 Nachkommenselektoren h2 a, h3 a, h4 a und der Deklaration display:none versteckt. Diese Regel wird vor der Regel mit den Attributselektoren notiert, sodass der Wert der display-Eigenschaft durch sie überschrieben wird, sofern der Browser sie versteht.

    h2 a, h3 a, h4 a {
     display:none;
    }
  6. Das a-Blockelement bewirkt einen Abstand mit der über die height-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 dem hX-Element, welches das a-Blockelement umfasst, und dem sich darüber befindenden Inhalt auf 0 (Null) zu setzen. Dies wird durch die Deklaration margin-top:0 erreicht, welche allen relevanten Kapitelüberschriften zugewiesen wird. Des weiteren sollte das hX-Ü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 das id-Attribut im Überschriftelement nötig war.

    /* Betrifft Überschriften mit einem id-Attribut: */
    h2[id], h3[id], h4[id] {
     margin-top:0;
     padding-top:0;
    }
  7. Auch dem Element, welches vor der Kapitelüberschrift liegt (beispielsweise ein p-Absatzelement), sollte ausdrücklich ein unterer Außenabstand margin-bottom mit dem Wert 0 zugewiesen werden. Um dies einfach ohne Eingriffe in den Code lösen zu können, kann der Außenabstand aller p-Elemente sowie anderer vorkommender Blockelemente (zum Beispiel ol, 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.

Browser-Unterstützung der ersten CSS-Lösung
Mozilla Milestone 16 (Gecko/20000613) Windows und darüberpositiv getestet
Opera 5.12 WindowsNegativ getestet. Das Blockelement wird zwar erzeugt und wie gewünscht angezeigt, aber der Text danach angesprungen.
Opera 6.00 WindowsNegativ getestet, der Überschriftstext wird angesprungen.
Opera 6.01 Windows und darüberpositiv getestet
Safari 1.2 MacOS Xpositiv getestet
Konqueror 3.2.2 Linuxpositiv 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 ein p-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 beim hX-Element, zum anderen durch das a-Element mit name-Attribut in diesem Überschriftenelement. Das Attribut name bei a-Elementen sowie das id-Attribut teilen sich ein und denselben Namensraum. Das Definieren von <a name="Name"></a> und die Angabe von id="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.

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.

  1. CSS 2 bietet die Möglichkeit, über die sogenannten Pseudo-Elemente :before und :after in Zusammenspiel mit der Eigenschaft content Inhalte einzufügen, unter anderem Text. Die Idee ist nun, das in der ersten CSS-Lösung zum Blockelement umformatierten a-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.

  2. 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 Typselektoren h2, h3 beziehungsweise h4 jeweils kombiniert mit dem Attributselektor [id] werden nur diejenigen Überschriftelemente ausgewählt, welche ein id-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 Überschriftelemente id-Attribute enthielten, erübrigte sich der genannte Attributselektor. Er sollte aber auch dann verwendet werden, um die Hürden einheitlich zu halten, siehe unten.

  3. 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 Deklaration content:'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, also content:' '. (Offenbar genügt auch eine leere Zeichenkette, das heißt content:''.)

    Illustration: Dem Überschriftelement wird zur Verdeutlichung ein roter Rahmen gegeben, der eingefügten Text (content:'Beispiel ') ist blau und hat einen hellgrauer Hintergrund.

  4. 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 Deklaration display: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.
  5. 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. */
    }
  6. 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 und padding-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 und content:'Text' unterstützen.

  7. 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.

Browser-Unterstützung der zweiten CSS-Lösung
Mozilla Milestone 16 (Gecko/20000613) Windows und darüberpositiv getestet
Opera 5.12 bis 6.06 WindowsNegativ getestet. Der Überschriftstext wird gesprungen, nicht die Oberkante der eingefügten Block-Box.
Opera 7.02 Windows und darüberpositiv getestet
Safari 1.2 MacOS X und Konqueror 3.2.2 LinuxNegativ 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?]

Dritte CSS-Lösung: Relativ positionierter Anker

  1. Wie in der ersten Lösung wird zunächst ein a-Element mit Ankerfunktion an den Anfang des hX-Elements gesetzt, welches lediglich ein geschütztes Leerzeichen enthält. Es ist ebenfalls der erste Kindknoten. Ein id-Attribut für das h2-Element ist jedoch nicht unbedingt notwendig, es kann im Falle von XHTML 1.0 wie gewohnt dem a-Element vergeben werden.
    <h2><a name="sprungstelle">&nbsp;</a>Beispielüberschrift</h2>
  2. 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 von position:fixed-fähigen Browsern verwendet.
  3. Über display:block wird das Ankerelement als Blockelement formatiert, sodass es im hX-Element über der Zeilenbox des Überschriftstext liegt. Dies ist vor allem notwendig, um später die Größe des Elements anzupassen.
  4. Der als Blockelement dargestellte Anker wird mittels position:relative und der Eigenschaft top (oder bottom) 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 mit top oder als positive Größe zusammen mit bottom verwendet werden, also beispielsweise top:-60px oder bottom:60px.
  5. Durch die relative Positionierung liegt die Box des a-Elements um die angegebene Höhe oberhalb der Box des hX-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 mittels background-color:black, sichtbar gemacht werden.

    Bei einer Verschiebung mittels position:relative wird die Elementbox zwar verrückt, der ursprünglich eingenommene Raum an der sogenannten Position im normalen Fluss bleibt reserviert, das heißt, er bleibt leer und transparent. Die Position des a-Elements im normalen Fluss liegt wie gesagt durch die Deklaration display:block innerhalb des hX-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 mittels height:0 die Höhe 0 (Null) zugewiesen. Die Breite wird mittels width: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 denen width:0 nicht wirksam ist, kann zusätzlich font-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.

  6. 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, wird visibility:hidden angegeben.

    Illustration: Beim Markieren des Texts wird das Ankerelement sichtbar.

    Durch visibility:hidden wird es vollends versteckt.

  7. 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.

Browser-Unterstützung der dritten CSS-Lösung
Mozilla 0.9.9 Windowsnegativ getestet.
Mozilla 1.0 Release Candidate 1 Windows und darüberpositiv getestet
Opera 6.00 Windowsnegativ getestet.
Opera 6.01 Windows und darüberpositiv getestet
Safari 1.2 MacOS Xnegativ getestet.
Konqueror 3.2.2positiv 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.

Verschiebung beim Anspringen eines Ankers im Opera 7.x im Quirks-Modus bei font-size:2px; line-height:0
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.

Größe des verschobenen Ankerelements je nach Mindestschriftgröße im Mozilla < 1.3 bei font-size:2px; line-height:0 (zur Veranschaulichung ohne visibility:hidden)
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

Vierte CSS-Lösung: Negativer Außenabstand und kompensierender Innenabstand

  1. Wie auch die dritte Lösung käme auch die folgende ohne a-Elemente mit name-Attributen aus. Aus den beschriebenen Gründen wird dennoch zur Abwärtskompatibilität bei jedem Anker zusätzlich ein leeres a-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>
  2. 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 Innenabstand padding-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 negativer margin-top vergeben worden.

    Illustration: Die Überschrift erhält einen negativen margin-top und einen positiven gleich großen padding-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.

  3. Die besagten Deklarationen werden in einer Regel mit dem Selektor h2[id], h3[id], h4[id] untergebracht. Betroffen sind dann h2-, h3- und h4-Elemente mit einem id-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. */
}
Browser-Unterstützung der vierten CSS-Lösung
Opera 5.12 bis 6.06 Windowsnegativ getestet
Opera 7.02 Windows und darüberpositiv getestet
Mozilla M16 Windowspositiv 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 Windowspositiv 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 Windowspositiv getestet (ohne Einschränkungen)
Safari 1.2 MacOS Xpositiv getestet
Konqueror 3.2.2 Linuxpositiv 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">&nbsp;</a>Beispielüberschrift</h2>
h2 a[name] {
 padding-top:60px; /* Dieser Wert kann variieren. */
 width:0;
 font-size:0;
}

[Erklärung fehlt]

Browser-Unterstützung der fünften CSS-Lösung
Opera 5.12 bis 6.06 Windowsnegativ getestet
Opera 7.02 Windows und darüberpositiv getestet
Mozilla M16 Windowspositiv 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 Windowsnegativ getestet
Mozilla 1.0 Release Candidate 1 Windows und darüberpositiv 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 Linuxnegativ 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).

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_uri, document_uri,  hash_position,  pattern;

    /* Hier wäre Platz für einen Algorithmus, welcher bestimmt, ob
    der Browser position: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, wenn scrollBy() 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_uri = location.href;
    hash_position = document_uri.indexOf("#");
    if (hash_position > -1) {
        /* Falls die aktuelle Adresse einen Anker enthält, schneide ihn ab */
        document_uri = document_uri.substring(0, hash_position);
    }
    /* Falls es sich um ein lokales Dokument handelt (file-Protokoll),
    vereinheitliche die Schreibweise */
    pattern = new RegExp("file:///");
    document_uri = document_uri.replace(pattern, "file://localhost/");

    /* Durchlaufe alle Hyperlinks des Dokuments */
    for (i = 0; i < linkcount; i++) {

       /* Gehe zum nächsten Link über, falls die hash-Eigenschaft
       leer ist oder nur # enthält */
        if (!hyperlinks[i].hash || hyperlinks[i].hash == "#")
            continue;

        /* Konstruiere die Zieladresse des Links ohne Anker */
        link_uri = hyperlinks[i].href;
        hash_position = link_uri.indexOf("#");
        /* Falls die Zieladresse einen Anker enthält, schneide ihn ab */
        if (hash_position > -1) {
            link_uri = link_uri.substring(0, hash_position);
        }
        /* Vereinheitlichung, falls es sich um einen Link auf
        ein lokales Dokument handelt */
        if (link_uri.indexOf("file://") == 0) {
            link_uri = link_uri.replace(pattern, "file://localhost/");
        }

        /* Vergleiche das Linkziel mit der aktuellen Adresse */
        if (link_uri == document_uri) {
            /* 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 Funktion init_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 der URI 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:

Vergleich der Werte der unterschiedlichen Objekteigenschaften bei verschiedenen Browsern
Browser location location.href location.hash testlink testlink.href testlink.hash testlink.getAttribute('href')
Netscape 4.51, 4.8 [voller URI]#testlink [voller URI]#testlink #testlink [voller URI]#testlink [voller URI]#testlink #testlink n/a
Opera 5.12, 6.0, 6.01, 6.06 [voller URI]#testlink [voller URI]#testlink #testlink [voller URI] [voller URI] #testlink [voller URI]
Opera 7.02, 7.11, 7.23 [voller URI]#testlink [voller URI]#testlink #testlink [voller URI] [voller URI]#testlink #testlink [voller URI]#testlink
Internet Explorer 5.0, 5.5, 6.0, Opera 7.50, 8.0 [voller URI]#testlink [voller URI]#testlink #testlink [voller URI]#testlink [voller URI]#testlink #testlink [voller URI]#testlink
Gecko, Konqueror, Safari [voller URI]#testlink [voller URI]#testlink #testlink [voller URI]#testlink [voller URI]#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_uri und C. der Variable document_uri. 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

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 Ereignisobjektes 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_uri;
    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_uri = target.href;
    hashposition = link_uri.indexOf("#");
    if (hashposition > -1) {
        link_uri = link_uri.substring(0, hashposition);
    }
    if (link_uri == document_uri) {
        window.setTimeout('window.scrollBy(0, -60)', 100);
    }
}
function init_position_fixed () {
    if (location.hash)
        window.scrollBy(0, -60);
    document_uri = location.href;
    var hashposition = document_uri.indexOf("#");
    if (hashposition > -1) {
        document_uri = document_uri.substring(0, hashposition);
    }
    document.addEventListener("click", handle_internal_link, false);
}
if (window.addEventListener && document.addEventListener)
    window.addEventListener("load", init_position_fixed, false);

Quelltext herunterladen

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

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: 04.12.2005 — Version 2.9