mitmario.dev

position: fixed

CSS Im Browser 4 Min Lesezeit 3 BeispieleLektion 4 von 8

position: fixed funktioniert fast genauso wie absolute: das Element ist aus dem Fluss, sein Platz verschwindet, und die vier Seitenangaben bestimmen, wo es sitzt. Der Unterschied steckt im Bezugsrahmen.

Bezug ist der Ausschnitt, nicht das Dokument

Am Viewport verankert
body {
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

p {
  margin: 0 0 64px 0;
}

/* Der Bezug ist der sichtbare Ausschnitt, nicht das
   Dokument. Deshalb bleibt der Knopf beim Scrollen
   genau dort, wo er ist. */
.knopf {
  position: fixed;
  top: 12px;
  right: 12px;
  padding: 8px 16px;
  border-radius: 999px;
  color: #ffffff;
  background-color: #2563eb;
}

Ein fixiertes Element bezieht sich auf den sichtbaren Ausschnitt des Fensters, nicht auf das Dokument und nicht auf ein Elternelement. Deshalb scrollt es nicht mit.

Die typischen Fälle kennst du von jeder zweiten Seite: der Cookie-Hinweis unten, der Zurück-nach-oben-Knopf in der Ecke, die Aktionsleiste auf dem Telefon, der Chat-Knopf, den niemand wollte.

Für all das ist fixed die richtige Wahl, und zwar genau dann, wenn das Element unabhängig vom Inhalt immer erreichbar sein soll. Eine Kopfzeile, die nur beim Scrollen im eigenen Abschnitt mitlaufen soll, ist dagegen ein Fall für sticky aus der nächsten Lektion.

Der Vorfahr mit transform

Ein Vorfahr mit transform
body {
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

p {
  margin: 0 0 64px 0;
}

/* Sieht harmlos aus und verschiebt um null Pixel.
   Trotzdem wird dieser Kasten damit zum Bezugsrahmen
   für alles, was darin fixiert ist. */
.huelle {
  transform: translateX(0);
}

.knopf {
  position: fixed;
  top: 12px;
  right: 12px;
  padding: 8px 16px;
  border-radius: 999px;
  color: #ffffff;
  background-color: #b91c1c;
}

Und hier ist die Ausnahme, die die Regel oben aufhebt und regelmäßig für einen Browser-Fehler gehalten wird.

Ein Vorfahr mit transform, filter, perspective, backdrop-filter oder will-change wird selbst zum Bezugsrahmen für fixierte Nachfahren. Das fixierte Element verhält sich dann faktisch wie ein absolut positioniertes und scrollt mit.

Im Beispiel steht am Container ein transform: translateX(0). Es verschiebt um null Pixel und ändert optisch nichts. Trotzdem scrollt der rote Knopf mit, während der blaue im Beispiel darüber stehen bleibt.

Sichtbar ist die Ursache nur im Streifen. Zeig auf die Hülle: Im Reiter „Werte” steht transform: matrix(1, 0, 0, 1, 0, 0). Das ist dieselbe Angabe, ausgerechnet, und die vier Einsen und Nullen sind die Einheitsmatrix, also „verschiebe um nichts, dreh um nichts, skaliere nicht”. Im Reiter „Regeln” steht daneben, was jemand geschrieben hat: transform: translateX(0px). Genau diese Zeile, die nichts tut, macht den Rahmen auf.

Wenn also ein fixiertes Element mitscrollt, ist der Weg immer derselbe: die Vorfahren durchgehen und im Streifen nach einer transform-Zeile suchen. Sie steht dort nur, wenn wirklich eine da ist.

Das ist keine Marotte, sondern ergibt sich aus der Sache: ein transformierter Container definiert ein eigenes Koordinatensystem, und ein Kind darin kann sich nicht sinnvoll auf ein anderes beziehen.

In der Praxis fällt das auf, wenn eine Animation dazukommt. Jemand baut ein Menü, das sanft aufklappt, schreibt transform an einen Container, und drei Ebenen darunter rutscht auf einmal ein fixierter Knopf. Wenn dir das passiert, such nicht bei dem fixierten Element, sondern geh die Vorfahren durch und halte nach genau diesen fünf Eigenschaften Ausschau.

Der Platz, den es nicht freimacht

Die Leiste verdeckt den Schluss
body {
  margin: 0;
  padding: 16px;
  font-family: system-ui, sans-serif;
  line-height: 1.6;

  /* Nimm diese Zeile heraus, dann verschwindet der
     letzte Absatz hinter der Leiste. Ein fixiertes
     Element macht sich im Fluss keinen Platz frei. */
  padding-bottom: 80px;
}

p {
  margin: 0 0 64px 0;
}

/* Der letzte Absatz gibt seinen unteren Rand ab.
   Mit den 64px von oben bliebe er sonst auch ohne
   padding-bottom von der Leiste frei. */
.letzter {
  margin-bottom: 0;
  background-color: #fef9c3;
}

.leiste {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 16px;
  text-align: center;
  color: #ffffff;
  background-color: #18181b;
}

Wie jedes Element aus dem Fluss macht auch ein fixiertes keinen Platz für sich frei. Eine Leiste am unteren Rand liegt deshalb über dem Inhalt, und die letzte Zeile der Seite ist nicht mehr zu lesen. Die Höhe, die du zurückgeben musst, steht im Streifen: Zeig auf die Leiste, und oben steht rund 380 × 58. Der body im Beispiel gibt 80 Pixel zurück, also ein paar mehr, und so macht man es auch: lieber etwas Luft als eine Zeile, die halb darunter verschwindet.

Die Lösung ist so unspektakulär, wie sie klingt: gib den Platz von Hand zurück. padding-bottom am body in ungefähr der Höhe der Leiste, und der Inhalt endet wieder dort, wo er soll.

Dasselbe gilt oben für eine fixierte Kopfzeile, dort mit padding-top. Wer das vergisst, versteckt die erste Überschrift der Seite hinter der eigenen Navigation, und das fällt oft erst auf, wenn jemand einen Anker-Link benutzt.

Auf dem Telefon ist es enger

Zum Schluss der Teil, den man auf einem großen Monitor nie sieht. Auf einem Telefon ist der sichtbare Ausschnitt klein, und eine fixierte Leiste frisst davon einen spürbaren Anteil. Zwei fixierte Leisten, oben und unten, lassen von einem kleinen Bildschirm wenig übrig.

Dazu kommt die Adressleiste des Browsers, die beim Scrollen ein- und ausfährt und dabei die Höhe des Ausschnitts ändert. Ein Element mit bottom: 0 wandert deshalb auf Mobilgeräten mit, und wer die Höhe in vh gerechnet hat, bekommt Sprünge. Die ehrlichere Einheit dafür heißt dvh und stand in Lektion 5.1.

Die Faustregel ist deshalb: fixiere so wenig wie möglich, und wenn, dann eins. Alles, was ständig sichtbar ist, kostet Platz, den der Inhalt eigentlich brauchen würde.

Zum Mitnehmen

Ein Vorfahr mit transform, filter oder will-change wird selbst zum Bezugsrahmen. Ein fixed-Element darin scrollt plötzlich mit, und das sieht so sehr nach einem Browser-Fehler aus, dass man überall sucht, nur nicht dort.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.