jQuery fixed on top für Contao

Anleitung für Contao 5.3

Manche Elemente sollen einfach nicht mitscrollen: die Navigation, ein Suchfeld, die Telefonnummer im Kopfbereich – alles, was Besucher jederzeit greifbar haben sollen. „Fixed on top“ heißt das Muster, und es gehört zu den meistgewünschten Kleinigkeiten in Website-Projekten.

Für Contao existieren dafür zwei kompakte jQuery-Lösungen, die sich als Templates einbinden lassen: j_sticky_fixed_header.html5 und j_sticky_fixed_top.html5. Dieser Beitrag zeigt beide im Detail – und im Update weiter unten den moderneren Weg, der ganz ohne jQuery auskommt.

Funktionsweise der Scripte

1. Script j_sticky_fixed_header.html5

Dieses Script sorgt dafür, dass ein Element mit der Klasse .fixed-on-top nicht direkt am oberen Bildschirmrand fixiert wird, sondern unterhalb des Elements #header.

Der Ablauf im Detail:

  • Das Script ermittelt die ursprüngliche Position des fixierten Elements sowie die Höhe des Headers (#header).
  • Beim Scrollen prüft jQuery, ob der Scrollwert größer ist als die Ausgangsposition des Elements abzüglich der Headerhöhe.
  • Falls ja, erhält das Element .fixed-on-top die zusätzliche CSS-Klasse .fixed und wird mit top: header direkt unterhalb des Kopfbereichs positioniert.
  • Gleichzeitig wird für das nachfolgende Element mit .fixed-on-top-item ein Abstand in Höhe des fixierten Elements erzeugt. Dadurch verschiebt sich das Layout nicht unkontrolliert nach oben.
  • Wird wieder nach oben gescrollt, entfernt das Script alle gesetzten Klassen und Styles, sodass das Element an seiner ursprünglichen Position steht.

Einsatzgebiet:

Ideal für Webseiten mit einem festen Kopfbereich (Logo, Navigation), bei denen weitere Inhalte darunter dauerhaft sichtbar bleiben sollen.

<script>
$(document).ready(function() {
  var top = $('.fixed-on-top').offset().top;
  var header = $('#header').height();
  var height = $('.fixed-on-top').height();
  $(window).scroll(function(){
	if ($(this).scrollTop() > top - header) {
		$('.fixed-on-top').addClass('fixed');
		$('.fixed-on-top').css('top', header);
		$('.fixed-on-top-item').addClass('fixed');
		$('.fixed-on-top-item').css('margin-top', height);
	} else {
		$('.fixed-on-top').removeClass('fixed');
		$('.fixed-on-top').css('top', '');
		$('.fixed-on-top-item').removeClass('fixed');
		$('.fixed-on-top-item').css('margin-top', '');
	}
  });
});
</script>

2. Script j_sticky_fixed_top.html5

Dieses Script arbeitet ähnlich, verzichtet jedoch auf den Bezug zum Header. Das Element wird direkt am oberen Bildschirmrand fixiert.

Der Ablauf im Detail:

  • Ausgangsposition und Höhe des Elements werden bestimmt.
  • Sobald der Scrollwert größer ist als die ursprüngliche Position, wird das Element mit .fixed-on-top fixiert.
  • Auch hier erhält das nachfolgende Element .fixed-on-top-item einen Abstand, um Layout-Verschiebungen zu verhindern.
  • Beim Zurückscrollen wird das Element wieder an seiner ursprünglichen Stelle angezeigt.

Einsatzgebiet:

Geeignet für Inhalte, die unabhängig vom Header jederzeit am oberen Bildschirmrand sichtbar sein sollen – beispielsweise eine fixe Call-to-Action-Leiste oder ein Hinweisbanner.

<script>
$(document).ready(function() {
  var top = $('.fixed-on-top').offset().top;
  var height = $('.fixed-on-top').height();
  $(window).scroll(function(){
	if ($(this).scrollTop() > top ) {
		$('.fixed-on-top').addClass('fixed');
		$('.fixed-on-top').css('top');
		$('.fixed-on-top-item').addClass('fixed');
		$('.fixed-on-top-item').css('margin-top', height);
	} else {
		$('.fixed-on-top').removeClass('fixed');
		$('.fixed-on-top').css('top', '');
		$('.fixed-on-top-item').removeClass('fixed');
		$('.fixed-on-top-item').css('margin-top', '');
	}
  });
});
</script>

Unterschiede im Überblick

Script Fixierung an Position Typischer Anwendungsfall
j_sticky_fixed_header.html5 Direkt unterhalb des #header Navigations- oder Funktionsleisten unterhalb der Hauptnavigation
j_sticky_fixed_top.html5 Ganz oben am Bildschirmrand Banner, Hinweisleisten, schmale Toolbars

Einbindung in Contao

Die Integration in Contao erfolgt in drei einfachen Schritten:

  1. Template anlegen
    • Im Contao-Template-Ordner eine Kopie von j_sticky_fixed_header.html5 oder j_sticky_fixed_top.html5 erstellen.
    • Das gewünschte Script einfügen.
  2. Template einbinden
    • Im Seitenlayout im Bereich jQuery-Templates das zuvor angelegte Template auswählen.
  3. Klasse vergeben
    • Dem gewünschten Element im Inhaltselement die CSS-Klasse .fixed-on-top hinzufügen.
    • Optional: Das nächste Element erhält die Klasse .fixed-on-top-item, damit der Layoutfluss korrekt bleibt.

CSS-Beispiel für die Fixierung

Damit die Funktion korrekt arbeitet, ist ein kurzes CSS notwendig:

.fixed {
  position: fixed;
  width: 100%;
  z-index: 1000;
}

Dieses Beispiel fixiert das Element dauerhaft sichtbar, sorgt für volle Breite und garantiert mit z-index, dass es nicht von anderen Elementen überlagert wird.

Ohne jQuery: derselbe Effekt mit CSS und drei Zeilen JavaScript

Update 2026: Die gezeigte Lösung funktioniert weiterhin – setzt aber jQuery voraus. Für dieselbe Aufgabe braucht es heute weder eine Bibliothek noch einen Scroll-Listener: CSS erledigt den Hauptteil, ein paar Zeilen reines JavaScript den Rest.

Variante 1: reines CSS – position: sticky

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg, #fff);
}

/* Sprungmarken landen nicht mehr unter dem Header */
:target { scroll-margin-top: 6rem; }

/* Kein Ruckeln beim Umschalten des Zustands */
@media (prefers-reduced-motion: no-preference) {
  .header { transition: box-shadow .2s ease, padding .2s ease; }
}

Das genügt für den Standardfall: Der Header bleibt oben kleben, sobald er den oberen Rand erreicht – ohne Skript, ohne Layoutsprung, ohne Kosten für die Ladezeit. Der frühere jQuery-Ansatz musste die Höhe des Headers messen und einen Platzhalter einfügen; sticky braucht beides nicht.

Variante 2: Zustand erkennen, ohne Scroll-Listener

Wer den Header beim Scrollen optisch verändern will – kleiner, mit Schatten –, braucht einen Zustand. Ein Scroll-Listener feuert dabei hunderte Male pro Sekunde und ist der klassische Grund für schlechte INP-Werte. Der IntersectionObserver löst das eleganter:

const wache = document.createElement("div");   // unsichtbarer Wächter
wache.style.cssText = "position:absolute;top:0;height:1px;width:1px";
document.body.prepend(wache);

const header = document.querySelector(".header");

new IntersectionObserver(
  ([eintrag]) => header.classList.toggle("is-sticky", !eintrag.isIntersecting),
  { threshold: 0 }
).observe(wache);
.header.is-sticky {
  box-shadow: 0 2px 12px rgb(0 0 0 / .12);
  padding-block: .4rem;   /* etwas kompakter */
}

Der Unterschied ist messbar: Statt bei jedem Scroll-Ereignis Rechenzeit im Hauptthread zu verbrauchen, meldet sich der Browser genau zweimal – beim Verlassen und beim Wiedererreichen der Startposition.

Was dabei oft vergessen wird

  • Ein fixierter Header verdeckt bei Sprungmarken den Zielbereich – scroll-margin-top löst das ohne JavaScript.
  • Auf kleinen Bildschirmen frisst ein sticky Header wertvolle Höhe. Ab einer bestimmten Breite abschalten ist oft die bessere Entscheidung als ihn zu verkleinern.
  • Wer die Höhe des Headers per JavaScript animiert, produziert Layoutverschiebungen (CLS). Änderungen gehören in transform oder in feste Höhen.
  • jQuery nur für diesen Effekt zu laden, kostet rund 30 Kilobyte – bei einer Aufgabe, die CSS in vier Zeilen erledigt.

Fazit

Beide Wege führen zum Ziel – aber sie stammen aus verschiedenen Epochen. Die jQuery-Templates funktionieren zuverlässig und sind in bestehenden Contao-Installationen schnell nachgerüstet; wo jQuery ohnehin schon geladen wird, spricht wenig dagegen.

Bei neuen Projekten hat sich das Blatt gedreht: position: sticky erledigt den Standardfall in vier Zeilen CSS, der IntersectionObserver liefert den Rest ohne Scroll-Listener – schneller, sparsamer und ohne 30 Kilobyte Bibliothek im Gepäck. Die ehrliche Empfehlung lautet deshalb: Bestand pflegen, Neues ohne jQuery bauen.

Zurück zur Blog-Übersicht