mitmario.dev

position: fixed

CSS Im Browser 4 Min Lesezeit 3 BeispieleLektion 4 von 8

Unten rechts in der Ecke bleibt ein Button stehen, während du scrollst. Dahinter steckt position: fixed, und das funktioniert fast wie absolute. Das Element ist aus dem Fluss, und sein Platz verschwindet. Wo es sitzt, bestimmen die vier Seitenangaben. Der Unterschied steckt im Bezugsrahmen.

Bezug ist der Viewport, 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 Viewport, nicht das
   Dokument. Deshalb bleibt der Button beim Scrollen
   genau dort, wo er ist. */
.button {
  position: fixed;
  top: 12px;
  right: 12px;
  padding: 8px 16px;
  border-radius: 999px;
  color: #ffffff;
  background-color: #2563eb;
}

Ein fixiertes Element bezieht sich auf den Viewport und nicht auf das Dokument oder einen Elternteil. Deshalb scrollt es nicht mit.

Typische Fälle sind der Cookie-Hinweis unten, der Zurück-nach-oben-Button in der Ecke, die Aktionsleiste auf dem Telefon und der Chat-Button, den niemand wollte.

Für all das ist fixed die richtige Wahl, wenn das Element unabhängig vom Inhalt immer erreichbar sein soll. Soll eine Kopfzeile dagegen nur innerhalb ihres eigenen Abschnitts mitlaufen, ist das ein Fall für sticky aus der nächsten Lektion.

Der Vorfahre mit transform

Ein Vorfahre 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);
}

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

Eine Ausnahme hebt diese Regel auf, und man hält sie leicht für einen Fehler des Browsers.

Ein Vorfahre mit transform, filter, perspective oder backdrop-filter wird selbst zum Bezugsrahmen für fixierte Nachfahren. Dasselbe gilt für translate, rotate und scale aus Abschnitt 13 und für ein paar seltene wie contain. Auch ein will-change, das eine dieser Eigenschaften ankündigt, zählt dazu, etwa will-change: transform. 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 Button mit. Der blaue im Beispiel darüber bleibt stehen.

Sichtbar ist die Ursache nur im Streifen. An der Hülle steht im Reiter „Werte“ transform: matrix(1, 0, 0, 1, 0, 0). Das ist dieselbe Angabe, nur ausgerechnet. Die sechs Zahlen sind die Einheitsmatrix, also „verschiebe um nichts, dreh um nichts, skaliere nicht“. Im Reiter „Regeln“ steht daneben, was jemand geschrieben hat, transform: translateX(0px). Diese Zeile, die nichts tut, macht den Rahmen auf.

Scrollt ein fixiertes Element mit, gehst du die Vorfahren durch und siehst im Streifen nach. transform, translate, rotate und scale stehen im Reiter „Werte“, sobald eine davon gesetzt ist. filter, perspective, backdrop-filter und will-change stehen dort nicht, sondern nur im Reiter „Regeln“.

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 Button. Dann suchst du nicht am fixierten Element, sondern an seinen Vorfahren, und zwar nach den Eigenschaften aus der Liste oben.

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;

  /* Ohne diese Zeile 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. Den Platz gibst du von Hand zurück, mit einem padding-bottom am body in ungefähr der Höhe der Leiste. Dann endet der Inhalt wieder dort, wo er soll.

Wie viel Höhe zurück muss, steht im Streifen. Bei der Breite, mit der die Spalte startet, steht an der Leiste oben rund 380 × 58. Der body im Beispiel gibt 80 Pixel zurück, also ein paar mehr. Ich lasse lieber etwas Luft als eine Zeile, die halb darunter verschwindet.

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. Bei einem Anker-Link hilft das padding-top allerdings nicht. Das Ziel rutscht trotzdem unter die Kopfzeile. Dafür gibt es scroll-padding-top am html, und damit sagst du dem Browser, wie viel er beim Springen oben frei lassen soll.

Auf dem Telefon ist es enger

Auf einem großen Monitor fällt das nie auf. Auf einem Telefon ist der Viewport 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. Sie fährt beim Scrollen ein und aus und ändert dabei die Höhe des Viewports. Ein Element mit bottom: 0 wandert deshalb auf Mobilgeräten mit. Bei Höhen hast du die Wahl aus Lektion 5.1. 100vh bleibt fest und ragt anfangs unter die Leiste, 100dvh passt immer genau und zieht dafür mit, wenn die Leiste ein- oder ausfährt.

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

In der Aufgabe steht der Zurück-nach-oben-Button noch am Ende des Dokuments.

Zum Mitnehmen

Ein fixiertes Element hängt am Viewport und bleibt beim Scrollen stehen. Es belegt keinen Platz, also bekommt der body oben oder unten einen passenden Innenabstand. Steht an einem Vorfahren transform oder filter, wird der zum Bezugsrahmen, und das Element scrollt doch mit.

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.