aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Sticky Header: Leiste fixieren und beim Scrollen ausblenden

Ein Sticky Header ist eine Navigationsleiste, die beim Scrollen der Seite oben am Bildschirm bleibt. Meine Lieblingsvariante ist die „intelligente": Sie schiebt sich beim Herunterscrollen aus dem Weg und erscheint sofort wieder, sobald der Nutzer nach oben scrollt. Dieses Verhalten gibt vertikalen Platz zum Lesen zurück und hält die Navigation gleichzeitig stets in Reichweite. In dieser Anleitung baue ich zuerst die fixierte Leiste mit reinem CSS und füge dann Schritt für Schritt das Ausblendverhalten beim Scrollen hinzu, das die Scrollrichtung erkennt.

Der einfachste Weg: position: sticky

In den meisten Fällen brauchst du nicht einmal JavaScript. position: sticky hält das Element im normalen Fluss, fixiert es aber, sobald es die von dir gesetzte Schwelle erreicht. Du musst ihm einen top-Wert geben; ohne ihn klebt es nie.

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #fff;
}

Das war's. Die Leiste startet im Seitenfluss, fixiert sich, wenn sie die obere Kante berührt, und der restliche Inhalt scrollt darunter durch. Der Vorteil von sticky gegenüber fixed ist, dass das Element zunächst echten Platz einnimmt: Es überlappt den darunterliegenden Inhalt nicht, sodass du kein ausgleichendes padding-top hinzufügen musst.

Wenn sticky nicht funktioniert, liegt es fast immer an einem von zwei Gründen: Entweder hast du keine top-Schwelle angegeben, oder ein Elternelement hat overflow: hidden / auto / scroll. Das Zweite deaktiviert sticky stillschweigend, weil das Element nun an den Scroll-Kontext dieses Containers gebunden ist.

Ausblenden und Einblenden je nach Scrollrichtung

Für das wirklich gewünschte „intelligente" Verhalten müssen wir die Scrollrichtung kennen. Die Logik ist einfach: Vergleiche bei jedem Scrollen den aktuellen scrollY-Wert mit dem vorherigen. Ist er gewachsen, scrollt der Nutzer nach unten; ist er kleiner geworden, nach oben. Beim Herunterscrollen schieben wir die Leiste nach oben, um sie zu verbergen; beim Hochscrollen holen wir sie zurück.

.site-header {
  position: sticky;
  top: 0;
  transition: transform 0.25s ease;
}
.site-header.versteckt {
  transform: translateY(-100%);
}

Ich verwende transform: translateY(-100%) aus Performance-Gründen: Änderungen an transform und opacity werden auf der Compositor-Ebene des Browsers verarbeitet, das heißt, sie animieren flüssig mit 60 fps, ohne Layout oder Paint auszulösen. Denselben Effekt durch Ändern von top oder height zu erzielen ist deutlich teurer.

Den Scroll-Listener richtig schreiben

Das scroll-Ereignis feuert Dutzende Male pro Sekunde. Wenn du bei jedem Auslösen das DOM liest und schreibst, ruckelt die Seite. Die Lösung besteht darin, die Arbeit mit requestAnimationFrame auf den nächsten Render-Frame zu verschieben und mit einem Flag zu verhindern, dass sie mehr als einmal pro Frame läuft.

const header = document.querySelector('.site-header');
let letzterScroll = 0;
let wartet = false;

function aktualisieren() {
  const y = window.scrollY;

  // Ganz oben immer anzeigen
  if (y <= 0) {
    header.classList.remove('versteckt');
  } else if (y > letzterScroll && y > 80) {
    // Wir scrollen über die Schwelle nach unten -> verstecken
    header.classList.add('versteckt');
  } else if (y < letzterScroll) {
    // Wir scrollen nach oben -> anzeigen
    header.classList.remove('versteckt');
  }

  letzterScroll = y;
  wartet = false;
}

window.addEventListener('scroll', () => {
  if (!wartet) {
    window.requestAnimationFrame(aktualisieren);
    wartet = true;
  }
}, { passive: true });

Ein paar kleine Details machen diesen Code brauchbar:

  • { passive: true }: teilt dem Browser mit „dieser Listener ruft kein preventDefault auf", sodass er flüssiger scrollen kann, ohne zu blockieren.
  • Die Schwelle y > 80: verhindert, dass die Leiste in den ersten Pixeln oben auf der Seite flackert. Das Verstecken beginnt erst, wenn der Nutzer ein Stück nach unten gescrollt hat.
  • Oben immer anzeigen: die Prüfung y <= 0 garantiert, dass der Header sichtbar ist, wenn du zum Seitenanfang zurückkehrst.

Das Aussehen ändern, sobald er fixiert ist

Ein weiterer häufiger Wunsch: Der Header ist oben auf der Seite transparent/groß und bekommt nach dem Scrollen einen Hintergrund und schrumpft. Wir machen das, indem wir je nach Fixierungszustand eine Klasse umschalten. Ein sauberer Weg, das ohne ständiges Abfragen zu erkennen, ist der IntersectionObserver: Platziere ein 1px großes Wächter-Element direkt über dem Header; verlässt es den Bildschirm, ist der Header fixiert.

const waechter = document.querySelector('#header-waechter');
const io = new IntersectionObserver(([eintrag]) => {
  header.classList.toggle('fixiert', !eintrag.isIntersecting);
}, { threshold: 0 });
io.observe(waechter);
.site-header.fixiert {
  box-shadow: 0 2px 12px rgba(0,0,0,.08);
  backdrop-filter: blur(8px);
}

Das Schöne am IntersectionObserver ist, dass er dies ohne den Aufruf von getBoundingClientRect() bei jedem scroll-Ereignis tut und den Browser die Arbeit selbst planen lässt — was weitaus effizienter ist.

Barrierefreiheit und Mobil

Sticky Header verursachen Barrierefreiheitsprobleme, wenn sie nachlässig gebaut sind. Übersieh diese nicht:

  • Ankerlinks: Beim Klicken auf einen seiteninternen #abschnitt-Link kann das Ziel unter dem fixierten Header landen. scroll-margin-top: 80px; an den Zielabschnitten behebt das.
  • Bewegungspräferenz: Deaktiviere die transition innerhalb von @media (prefers-reduced-motion: reduce), damit sich die Leiste für bewegungsempfindliche Nutzer sofort ändert.
  • Tastaturfokus: Wenn Links in der Leiste per Tab den Fokus erhalten, während sie versteckt ist, sollte die Leiste sichtbar werden. Da wir mit transform statt display: none verstecken, funktioniert das bereits.
  • Höhe auf Mobilgeräten: Lass den Header auf kleinen Bildschirmen nicht zu viel vertikalen Platz verbrauchen; das Verstecken/Einblenden ist hier besonders wertvoll, weil es beim Lesen den ganzen Bildschirm zurückgibt.
@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}
section[id] { scroll-margin-top: 80px; }

Häufige Fragen

Was ist der Unterschied zwischen position: sticky und fixed?

fixed entfernt das Element vollständig aus dem normalen Fluss und fixiert es immer am Viewport; es überlappt den darunterliegenden Inhalt, daher brauchst du ein manuelles padding-top. sticky startet im Fluss, fixiert sich erst beim Erreichen der Schwelle und gibt frei, wenn sein Container endet. Für einen seitenbreiten Header ist sticky meist sauberer.

Der Header flackert/springt beim Scrollen — warum?

Meist zwei Gründe: Entweder hast du das scroll-Ereignis nicht mit requestAnimationFrame gedrosselt, oder es fehlt eine Verstecken/Anzeigen-Schwelle für die ersten Pixel oben. Eine Schwelle wie y > 80 plus rAF-Framing beseitigt dieses Zittern.

Lässt sich Verstecken/Anzeigen allein mit CSS umsetzen?

Nicht ganz. position: sticky erledigt die Fixierung in CSS, aber das Verhalten „beim Runterscrollen verstecken, beim Hochscrollen anzeigen" erfordert Kenntnis der Scrollrichtung, und ein kleines Stück JavaScript ist dafür heute noch der zuverlässigste Weg.

Verhält sich deine Header-Leiste nicht so, wie sie sollte? Wir können Sticky Header, Dropdown-Menüs und Schichtungs- (z-index) Probleme gemeinsam flüssig und barrierefrei machen. Wenn du ein Projekt hast, nimm Kontakt mit mir auf.

Bu kategorideki tüm yazılar →

Devamı için