CSS Grid Layout und Scroll-Reveal ohne Bibliothek
Eine vertikale Timeline Komponente zeigt Ereignisse chronologisch entlang einer senkrechten Linie und aktiviert Etappen beim Scrollen. Mit CSS Grid für das Layout und Alpine.js x-intersect für Scroll-Reveal Animationen entsteht eine flexible Timeline Komponente, ohne eine dedizierte Timeline Bibliothek einzubinden.
Inhaltsverzeichnis
- 1. Wozu eine vertikale Timeline Komponente sinnvoll ist
- 2. Grundstruktur: CSS Grid für Linie und Etappen
- 3. Datenstruktur: Ereignisse als Array modellieren
- 4. Scroll-Reveal mit x-intersect
- 5. Aktive Etappe hervorheben beim Scrollen
- 6. Alternierendes Layout für Desktop Ansichten
- 7. Responsive Verhalten: von zweispaltig zu einspaltig
- 8. Performance: IntersectionObserver statt Scroll Listener
- 9. Timeline Ansätze im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Wozu eine vertikale Timeline Komponente sinnvoll ist
Eine vertikale Timeline Komponente eignet sich für Unternehmensgeschichten, Projektverläufe, Produktroadmaps und Changelogs. Statt Ereignisse in einer schlichten Liste aufzuzählen, ordnet die vertikale Timeline sie entlang einer durchgehenden Linie an, wodurch die zeitliche Reihenfolge visuell sofort erfassbar wird. Viele Projekte greifen dafür zu fertigen Timeline Bibliotheken, obwohl das zugrunde liegende Layout mit CSS Grid und ein wenig Alpine.js für die Interaktivität in kurzer Zeit selbst gebaut ist.
Der Vorteil einer selbst gebauten vertikalen Timeline zeigt sich besonders bei individuellen Anforderungen: eigene Icons pro Etappe, ein alternierendes Layout, das auf breiten Bildschirmen abwechselnd links und rechts der Linie rendert, oder eine Scroll-Reveal Animation, die Etappen erst beim Erreichen im Sichtbereich einblendet. Fertige Bibliotheken decken oft nur einen Bruchteil dieser Anforderungen ab und bringen gleichzeitig eigenes CSS mit, das mit einem bestehenden Tailwind Setup kollidieren kann.
Dieser Artikel baut eine vollständige vertikale Timeline Komponente: CSS Grid für die Grundstruktur, ein Alpine.js Array für die Ereignisdaten, x-intersect für Scroll-Reveal Animationen und eine zusätzliche Logik, um die aktuell sichtbare Etappe optisch hervorzuheben.
2. Grundstruktur: CSS Grid für Linie und Etappen
Das Grundgerüst einer vertikalen Timeline lässt sich elegant mit CSS Grid umsetzen: eine Spalte für die durchgehende vertikale Linie mit den Punkten, eine zweite Spalte für den Inhalt jeder Etappe. Die Linie selbst ist ein einzelnes div mit fester Breite und einem Hintergrund, das über grid-row: 1 / -1 die volle Höhe aller Etappen überspannt, während jede Etappe ihren eigenen Punkt über position: relative und einen kleinen Kreis mittig auf der Linie platziert.
Diese Grid basierte Struktur einer vertikalen Timeline vermeidet die in älteren Tutorials verbreitete Technik mit absolut positionierten Pseudo Elementen und manuell berechneten Pixel Abständen. Mit Grid Zeilen, die sich automatisch an den Inhalt jeder Etappe anpassen, bleibt die Linie immer exakt so lang wie die Summe der Etappen, unabhängig davon, wie viel Text eine einzelne Etappe enthält.
<div x-data="verticalTimeline()" class="relative grid grid-cols-[2rem_1fr] gap-x-6">
<!-- Continuous line, spans the full grid height -->
<div class="col-start-1 row-span-full w-0.5 bg-teal-200 justify-self-center" style="grid-row: 1 / -1;"></div>
<template x-for="(item, index) in events" :key="item.id">
<div class="contents">
<!-- Dot marker, centered on the line -->
<div class="col-start-1 row-start-auto w-8 h-8 rounded-full bg-white border-2 border-teal-500 flex items-center justify-center self-start z-10">
<span class="w-2.5 h-2.5 rounded-full bg-teal-500"></span>
</div>
<!-- Event content -->
<div class="col-start-2 pb-10">
<p class="text-sm font-semibold text-teal-700" x-text="item.date"></p>
<p class="text-lg font-bold text-slate-900" x-text="item.title"></p>
<p class="text-sm text-slate-600" x-text="item.description"></p>
</div>
</div>
</template>
</div>
3. Datenstruktur: Ereignisse als Array modellieren
Die Datenstruktur einer vertikalen Timeline ist ein einfaches Array von Ereignis Objekten mit Datum, Titel, Beschreibung und optional einem Icon Namen. Wichtig ist ein stabiles id Feld pro Ereignis, da x-for diese ID für das :key Binding benötigt, um beim Neu Rendern die richtigen DOM Elemente wiederzuverwenden statt sie komplett neu zu erzeugen.
Zusätzlich zu den reinen Anzeigedaten braucht die vertikale Timeline für die Scroll-Reveal Funktion ein Feld isVisible, das zunächst bei allen Ereignissen auf false steht und erst gesetzt wird, sobald die jeweilige Etappe in den sichtbaren Bereich scrollt. Diese Trennung zwischen statischen Anzeigedaten und dynamischem Sichtbarkeitsstatus hält den Code übersichtlich und macht die Timeline leicht erweiterbar, etwa um Filterung nach Kategorie.
function verticalTimeline() {
return {
events: [
{ id: 1, date: '2023', title: 'Projektstart', description: 'Erste Konzeption und Anforderungsanalyse.', isVisible: false },
{ id: 2, date: '2024', title: 'Beta Launch', description: 'Erste Version für ausgewählte Kunden freigeschaltet.', isVisible: false },
{ id: 3, date: '2025', title: 'Vollständiger Rollout', description: 'Produktivbetrieb für alle Nutzer gestartet.', isVisible: false },
{ id: 4, date: '2026', title: 'Erweiterung', description: 'Neue Module und internationale Verfügbarkeit.', isVisible: false },
],
markVisible(eventId) {
const event = this.events.find((e) => e.id === eventId);
if (event) event.isVisible = true;
},
};
}
4. Scroll-Reveal mit x-intersect
Alpine.js Intersect ist das Plugin, das die vertikale Timeline mit Scroll-Reveal Animationen versieht, ohne dass eine externe Bibliothek wie AOS oder ScrollReveal geladen werden muss. Die Direktive x-intersect führt einen Ausdruck genau dann aus, wenn das Element in den sichtbaren Bereich des Viewports eintritt, technisch basierend auf der nativen IntersectionObserver API des Browsers.
Für die vertikale Timeline reicht ein einfacher x-intersect="markVisible(item.id)" pro Etappe, kombiniert mit einer :class Bindung, die je nach item.isVisible zwischen einem eingeblendeten und einem ausgeblendeten Zustand wechselt. Der Modifikator x-intersect.once stellt sicher, dass eine einmal eingeblendete Etappe beim erneuten Scrollen nicht wieder ausgeblendet wird, was für die meisten Timeline Anwendungsfälle das gewünschte Verhalten ist.
<template x-for="item in events" :key="item.id">
<div class="contents">
<div class="col-start-1 w-8 h-8 rounded-full bg-white border-2 border-teal-500 self-start z-10"></div>
<div
class="col-start-2 pb-10 transition-all duration-700"
x-intersect.once="markVisible(item.id)"
:class="item.isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'"
>
<p class="text-sm font-semibold text-teal-700" x-text="item.date"></p>
<p class="text-lg font-bold text-slate-900" x-text="item.title"></p>
<p class="text-sm text-slate-600" x-text="item.description"></p>
</div>
</div>
</template>
Ein häufiger Fehler bei der vertikalen Timeline mit x-intersect: Der initiale Zustand vor der Sichtbarkeit muss bereits über :class gesetzt sein, sonst blitzt der Inhalt kurz sichtbar auf, bevor die Übergangsanimation greift. Deshalb sollte die opacity-0 Klasse als Standardwert im Template stehen, nicht erst dynamisch über JavaScript nachträglich gesetzt werden.
5. Aktive Etappe hervorheben beim Scrollen
Über das reine Einblenden hinaus profitiert eine vertikale Timeline davon, die aktuell im Fokus stehende Etappe optisch hervorzuheben, ähnlich einem Fortschrittsbalken. Dafür wird zusätzlich zu x-intersect.once für die Einblendanimation ein zweiter, wiederholbarer Intersect Handler ohne once Modifikator genutzt, der eine activeEventId Variable im obersten State aktualisiert, sobald eine Etappe die Bildschirmmitte durchquert.
Die Threshold Konfiguration ist hier entscheidend: x-intersect:threshold.50="setActive(item.id)" löst erst aus, wenn mindestens die Hälfte des Elements sichtbar ist, was für die vertikale Timeline ein deutlich präziseres Ergebnis liefert als der Standard Threshold von null Prozent, bei dem bereits das erste sichtbare Pixel ausreicht.
function verticalTimeline() {
return {
events: [ /* ... */ ],
activeEventId: null,
setActive(eventId) {
this.activeEventId = eventId;
},
isActive(eventId) {
return this.activeEventId === eventId;
},
};
}
6. Alternierendes Layout für Desktop Ansichten
Auf breiten Bildschirmen wirkt eine vertikale Timeline oft eleganter, wenn Etappen abwechselnd links und rechts der Mittellinie erscheinen, statt alle auf derselben Seite zu stapeln. Diese Variante braucht eine dritte Grid Spalte, in der ungerade Etappen den linken Bereich und gerade Etappen den rechten Bereich nutzen, während die Linie exakt in der mittleren Spalte verbleibt.
Alpine.js berechnet die Seitenzuordnung direkt im Template über den Index der x-for Schleife: index % 2 === 0 bestimmt, ob eine Etappe der vertikalen Timeline links oder rechts platziert wird, kombiniert mit bedingten Tailwind Klassen für Textausrichtung und Grid Spalten Position.
<div x-data="verticalTimeline()" class="relative grid grid-cols-[1fr_2rem_1fr] gap-x-6 max-lg:grid-cols-[2rem_1fr]">
<div class="col-start-2 row-span-full w-0.5 bg-teal-200 justify-self-center max-lg:col-start-1" style="grid-row: 1 / -1;"></div>
<template x-for="(item, index) in events" :key="item.id">
<div class="contents">
<div
class="w-8 h-8 rounded-full bg-white border-2 border-teal-500 self-start z-10 justify-self-center max-lg:col-start-1"
:class="index % 2 === 0 ? 'col-start-2' : 'col-start-2'"
></div>
<div
class="pb-10 max-lg:col-start-2"
:class="index % 2 === 0 ? 'col-start-1 text-right' : 'col-start-3 text-left'"
>
<p class="text-sm font-semibold text-teal-700" x-text="item.date"></p>
<p class="text-lg font-bold text-slate-900" x-text="item.title"></p>
</div>
</div>
</template>
</div>
7. Responsive Verhalten: von zweispaltig zu einspaltig
Das alternierende Layout einer vertikalen Timeline funktioniert nur auf ausreichend breiten Bildschirmen, auf Mobilgeräten würde die geteilte Darstellung den ohnehin knappen horizontalen Platz zusätzlich einschränken. Deshalb fällt die Timeline unterhalb eines definierten Breakpoints, im Beispiel lg, automatisch auf ein einspaltiges Layout zurück, bei dem alle Etappen auf derselben Seite der Linie stehen.
Der Wechsel zwischen den Layouts geschieht rein über Tailwind Breakpoint Präfixe wie max-lg:col-start-1, ganz ohne zusätzliche JavaScript Logik oder window.matchMedia Abfragen in Alpine.js. Diese CSS only Lösung ist robuster als eine JavaScript basierte Breakpoint Erkennung, da sie ohne Verzögerung beim Resize reagiert und keinen zusätzlichen Reflow durch JavaScript Berechnungen verursacht.
8. Performance: IntersectionObserver statt Scroll Listener
Vor der IntersectionObserver API wurden Scroll-Reveal Effekte für eine vertikale Timeline fast ausschließlich über scroll Event Listener implementiert, die bei jedem Scroll Event die Position jedes Timeline Elements über getBoundingClientRect() neu berechneten. Bei einer langen Timeline mit vielen Etappen summierten sich diese Berechnungen zu spürbaren Performance Problemen, insbesondere auf mobilen Geräten mit begrenzter Rechenleistung.
x-intersect und die zugrunde liegende IntersectionObserver API arbeiten fundamental anders: der Browser überwacht die Sichtbarkeit der Elemente selbst, außerhalb des Haupt Threads, und benachrichtigt Alpine.js nur bei tatsächlichen Sichtbarkeitsänderungen. Für eine vertikale Timeline mit zwanzig oder mehr Etappen ist dieser Unterschied nicht nur eine theoretische Optimierung, sondern in der Praxis spürbar auf schwächerer Hardware.
9. Timeline Ansätze im Vergleich
Für eine vertikale Timeline Komponente existieren mehrere technische Umsetzungen mit unterschiedlichen Kompromissen.
| Ansatz | Layout Technik | Scroll Reveal | Eignung |
|---|---|---|---|
| CSS Grid plus Alpine.js x-intersect | Nativ, kein JS Layout | IntersectionObserver | Standardfall, alle Projektgrößen |
| Flexbox mit absoluter Linie | Manuelle Pixel Berechnung | Je nach Implementierung | Einfache, kurze Timelines |
| AOS oder ScrollReveal Bibliothek | Eigenes CSS System | Scroll Listener basiert | Nur bei bereits vorhandener Nutzung |
| SVG Pfad basierte Timeline | SVG path, komplex | Je nach Implementierung | Geschwungene, nicht lineare Verläufe |
Für die überwiegende Mehrheit der Anwendungsfälle liefert CSS Grid kombiniert mit Alpine.js x-intersect bei einer vertikalen Timeline das beste Verhältnis aus Wartbarkeit, Performance und Bundle Größe. Nur bei stark geschwungenen, nicht linearen Timeline Verläufen lohnt sich der zusätzliche Aufwand einer SVG Pfad basierten Lösung.
Mironsoft
Alpine.js Komponenten für Hyvä, Magento und individuelle Frontends
Eine eigene Timeline Komponente oder eine andere Alpine.js Lösung nötig?
Wir entwickeln individuelle Alpine.js Komponenten, von Timelines über Scroll-Reveal Sektionen bis zu komplexen Landingpage Elementen, sauber integriert in euer bestehendes Hyvä oder Magento Frontend.
Konzeption
Layout und Interaktionsmuster für eure Timeline klären
Umsetzung
CSS Grid, x-intersect und responsive Layout aus einer Hand
Integration
Saubere Einbindung in bestehende Hyvä und Magento Frontends
10. Zusammenfassung
Eine vertikale Timeline Komponente lässt sich vollständig mit CSS Grid für das Layout und Alpine.js für die Interaktivität umsetzen, ohne eine dedizierte Timeline Bibliothek zu laden. Grid Spalten übernehmen die Positionierung von Linie, Punkten und Inhalt, während ein einfaches Array von Ereignis Objekten die Datenstruktur bildet.
Scroll-Reveal Animationen kommen über x-intersect ins Spiel, das auf der nativen IntersectionObserver API basiert und damit deutlich performanter arbeitet als klassische Scroll Event Listener. Für Desktop Ansichten lohnt sich ein alternierendes Layout, das über den Index der x-for Schleife berechnet wird, während responsive Breakpoints die vertikale Timeline auf Mobilgeräten automatisch auf ein einspaltiges Layout zurückstufen.
Vertikale Timeline mit Alpine.js — Das Wichtigste auf einen Blick
Layout
CSS Grid mit Spalten für Linie und Inhalt, keine manuelle Pixel Berechnung nötig.
Scroll-Reveal
x-intersect.once blendet Etappen beim Erreichen des Viewports sanft ein.
Aktive Etappe
Threshold basierter Intersect Handler markiert die Etappe in der Bildschirmmitte.
Responsive
Tailwind Breakpoints schalten zwischen alternierendem und einspaltigem Layout um.