Named Timelines, View-Timeline und animation-range im Detail
Ein einfaches Fade-in beim Scrollen kennt inzwischen fast jedes Projekt. Wirklich interessant wird Scroll-Timeline aber erst, wenn mehrere Achsen, benannte Timelines und präzise animation-range-Werte zusammenspielen, um komplexe Erzähl-Layouts ganz ohne Scroll-Event-Listener zu bauen.
Inhaltsverzeichnis
- 1. Warum Named Scroll-Timelines fortgeschrittene Kontrolle brauchen
- 2. scroll-timeline-name und scroll-timeline-axis im Detail
- 3. View-Timeline für Element-Sichtbarkeit nutzen
- 4. animation-range: Start und Ende präzise setzen
- 5. Mehrere Timelines kombinieren: Parallax mit mehreren Achsen
- 6. timeline-scope für Cross-Element-Steuerung
- 7. Fallback mit @supports und IntersectionObserver
- 8. Debugging von Scroll-Timelines in den DevTools
- 9. Scroll-Timeline-Patterns im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Named Scroll-Timelines fortgeschrittene Kontrolle brauchen
Die einfachste Form einer Scroll-Timeline koppelt eine Animation direkt an den Scroll-Fortschritt des nächsten scrollenden Vorfahren: animation-timeline: scroll();. Für ein Fortschritts-Balken oder ein einfaches Parallax-Element reicht das völlig aus. Sobald aber mehrere unabhängige Scroll-Container auf einer Seite existieren, etwa ein horizontal scrollender Karussell-Bereich neben dem vertikalen Hauptdokument, wird die anonyme Timeline mehrdeutig: welches animierte Element soll sich auf welchen Container beziehen?
Genau hier setzen benannte Scroll-Timelines an. Mit scroll-timeline-name wird einem beliebigen scrollenden Element ein eindeutiger Bezeichner gegeben, der dann von jedem beliebigen anderen Element im Dokument über animation-timeline: --mein-name; referenziert werden kann, unabhängig von der DOM-Position. Diese Entkopplung von Quelle und Ziel ist der entscheidende Unterschied zwischen einem simplen Scroll-Effekt und einer wirklich fortgeschrittenen Scroll-Timeline-Architektur.
Ein praktisches Beispiel: ein Fortschrittsbalken am oberen Bildschirmrand soll den Lesefortschritt eines Artikelbereichs anzeigen, der selbst nicht scrollt, sondern in einem Container weiter unten liegt. Ohne benannte Timeline müsste der Balken ein Kind des scrollenden Containers sein. Mit scroll-timeline-name kann der Balken irgendwo im Dokument stehen und trotzdem exakt an den Scroll-Fortschritt des Containers gekoppelt werden.
2. scroll-timeline-name und scroll-timeline-axis im Detail
Die Kurzform scroll-timeline: --progress block; setzt in einem Schritt Name und Achse. scroll-timeline-axis bestimmt dabei, ob die Timeline dem Fortschritt entlang der Block-Achse (meist vertikal) oder der Inline-Achse (meist horizontal) folgt. Für horizontal scrollende Karussells oder Storytelling-Sektionen mit seitlichem Scroll ist inline die richtige Wahl, für den überwiegenden Teil klassischer Seiten block.
Wichtig für fortgeschrittene Scroll-Timeline-Setups: der Name selbst ist ein Custom-Ident, beginnt also mit zwei Bindestrichen, genau wie eine CSS-Custom-Property. Mehrere Elemente im Dokument können denselben Namen nutzen, ohne dass es zu einem Konflikt kommt, solange jedes referenzierende Element eindeutig weiß, welche Timeline gemeint ist. In der Praxis empfiehlt sich dennoch ein sprechendes, eindeutiges Namensschema pro Sektion, etwa --hero-scroll oder --gallery-scroll, um Verwechslungen in größeren Stylesheets zu vermeiden.
/* Scrolling container defines the named timeline */
.gallery-track {
overflow-x: auto;
scroll-timeline: --gallery-scroll inline;
}
/* Any element in the document can subscribe to it */
.gallery-progress-bar {
animation: fill-bar linear;
animation-timeline: --gallery-scroll;
animation-range: 0% 100%;
}
@keyframes fill-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Ein häufiger Fehler bei der Migration von anonymen zu benannten Scroll-Timelines: das Attribut scroll-timeline-axis ohne overflow auf demselben Element zu setzen. Die Timeline benötigt zwingend einen tatsächlich scrollenden Container, ein Element ohne overflow: auto oder overflow: scroll erzeugt keine gültige Scroll-Timeline, selbst wenn Name und Achse korrekt deklariert sind.
3. View-Timeline für Element-Sichtbarkeit nutzen
Während scroll-timeline den Fortschritt eines Containers misst, misst view-timeline den Sichtbarkeitsfortschritt eines einzelnen Elements innerhalb seines nächsten scrollenden Vorfahren. Das Element selbst wird zur Quelle der Timeline: sobald es in den sichtbaren Bereich eintritt, beginnt die Timeline bei 0 Prozent, verlässt es den sichtbaren Bereich vollständig, endet sie bei 100 Prozent. Das ist exakt das Muster, das man für Reveal-Animationen, Sticky-Highlight-Effekte oder Scrollytelling-Sektionen braucht.
Die Kurzform view-timeline: --card-reveal block; auf dem zu beobachtenden Element aktiviert diese Sichtbarkeits-Timeline direkt. Kombiniert mit view-timeline-inset lässt sich der Sichtbarkeitsbereich zusätzlich verschieben, etwa um eine Animation erst auszulösen, wenn ein Element schon ein Stück im Viewport steht, statt sofort an dessen Rand.
.story-card {
view-timeline: --card-reveal block;
view-timeline-inset: 10% 0%;
}
.story-card {
animation: reveal-card linear both;
animation-timeline: --card-reveal;
animation-range: entry 0% cover 40%;
}
@keyframes reveal-card {
from { opacity: 0; transform: translateY(48px) scale(0.94); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
Der entscheidende Unterschied zu einem einfachen Fade-in-Skript mit IntersectionObserver: view-timeline ist voll scrubbing-fähig. Der Fortschritt der Animation folgt exakt dem Scroll-Fortschritt, in beide Richtungen, ohne dass eine JavaScript-Callback-Kette zwischengeschaltet wäre. Scrollt der Nutzer schnell, folgt die Animation genauso schnell, scrollt er zurück, läuft die Animation rückwärts, alles allein durch den Compositor-Thread des Browsers gesteuert.
4. animation-range: Start und Ende präzise setzen
animation-range ist der Baustein, der aus einer groben Sichtbarkeits-Timeline eine präzise choreografierte Scroll-Timeline-Animation macht. Statt der gesamten Sichtbarkeitsdauer eines Elements lässt sich exakt festlegen, in welchem Ausschnitt der Timeline eine Animation läuft. Die benannten Bereiche entry, exit, cover und contain beschreiben unterschiedliche Phasen des Sichtbarkeitsverlaufs und lassen sich mit Prozentwerten kombinieren.
entry 0% entry 100% etwa beschreibt exakt die Phase, in der das Element von komplett unsichtbar zu komplett sichtbar wird, während cover 0% cover 100% die gesamte Zeitspanne beschreibt, in der irgendein Teil des Elements sichtbar ist. Diese Bereiche lassen sich beliebig mischen: animation-range: entry 20% cover 60%; startet die Animation, wenn das Element zu 20 Prozent in die Entry-Phase eingetreten ist, und beendet sie, wenn 60 Prozent der Cover-Phase erreicht sind.
/* Precise choreography: fade during entry, hold, then fade during exit */
.section-heading {
view-timeline: --heading-view block;
animation: heading-choreography linear both;
animation-timeline: --heading-view;
animation-range: entry 0% exit 100%;
}
@keyframes heading-choreography {
0% { opacity: 0; filter: blur(6px); }
20% { opacity: 1; filter: blur(0); }
80% { opacity: 1; filter: blur(0); }
100% { opacity: 0; filter: blur(6px); }
}
Der Vorteil dieser präzisen animation-range-Steuerung gegenüber einem groben Alles-oder-nichts-Fade: eine Sektion kann während des gesamten sichtbaren Zeitraums stabil bleiben und nur an den Übergängen animieren, was für Textabschnitte deutlich ruhiger wirkt als ein Effekt, der über die komplette Sichtbarkeitsdauer läuft. Wer Scroll-Timeline-Effekte produktionsreif einsetzen will, kommt an dieser Feinjustierung nicht vorbei.
5. Mehrere Timelines kombinieren: Parallax mit mehreren Achsen
Klassisches Parallax verschiebt Ebenen mit unterschiedlicher Geschwindigkeit relativ zum Scroll-Fortschritt. Mit mehreren gleichzeitig aktiven Scroll-Timelines lässt sich dieser Effekt vollständig ohne JavaScript umsetzen, indem jede Ebene eine eigene animation-timeline mit unterschiedlichem animation-range oder unterschiedlicher Keyframe-Distanz erhält. Die Hintergrundebene bewegt sich über die gesamte Scroll-Range um wenige Prozent, die Vordergrundebene über dieselbe Range um ein Vielfaches mehr.
Für echte Mehrachsen-Effekte, bei denen eine Ebene sich gleichzeitig vertikal verschiebt und horizontal rotiert, lassen sich sogar zwei unabhängige Animationen auf demselben Element an dieselbe Timeline koppeln, jede mit eigenem animation-range. Das Ergebnis ist ein choreografierter Effekt, der auf den ersten Blick nach komplexem JavaScript aussieht, tatsächlich aber vollständig deklarativ im Stylesheet steht.
.parallax-section {
scroll-timeline: --parallax-scroll block;
}
.parallax-bg {
animation: shift-bg linear both;
animation-timeline: --parallax-scroll;
animation-range: 0% 100%;
}
.parallax-mid {
animation: shift-mid linear both;
animation-timeline: --parallax-scroll;
animation-range: 0% 100%;
}
.parallax-fg {
animation: shift-fg linear both, rotate-fg linear both;
animation-timeline: --parallax-scroll, --parallax-scroll;
animation-range: 0% 100%, 10% 90%;
}
@keyframes shift-bg { to { transform: translateY(6%); } }
@keyframes shift-mid { to { transform: translateY(18%); } }
@keyframes shift-fg { to { transform: translateY(40%); } }
@keyframes rotate-fg { to { transform: rotate(4deg); } }
Der Performance-Vorteil ist dabei nicht zu unterschätzen: alle drei Ebenen laufen auf dem Compositor-Thread, ohne dass der Main-Thread bei jedem Scroll-Frame neu berechnen müsste. Ein klassisches JavaScript-Parallax mit scroll-Event-Listenern verursacht dagegen bei jedem einzelnen Scroll-Frame einen Layout-Thrash-Kandidaten, sobald getBoundingClientRect() im Handler aufgerufen wird.
6. timeline-scope für Cross-Element-Steuerung
Standardmäßig ist eine benannte Scroll-Timeline oder view-timeline nur innerhalb ihres eigenen Subtrees sichtbar, also für Nachfahren des Elements, das sie deklariert. Für Fälle, in denen ein Element außerhalb dieses Subtrees auf die Timeline reagieren soll, etwa ein Fortschrittsindikator im Header, der auf ein view-timeline eines Elements weit unten im Dokument reagiert, gibt es timeline-scope.
Deklariert auf einem gemeinsamen Vorfahren, etwa :root oder einem Layout-Wrapper, erweitert timeline-scope die Sichtbarkeit einer benannten Timeline auf den gesamten Scope, unabhängig von der tatsächlichen DOM-Verschachtelung. Das macht komplexe Scrollytelling-Layouts erst möglich, bei denen Steuerelemente und animierte Ziele an völlig unterschiedlichen Stellen im Dokument liegen.
:root {
timeline-scope: --hero-view;
}
.hero-section {
view-timeline: --hero-view block;
}
/* Header lives outside .hero-section in the DOM, but can still react */
.site-header .scroll-indicator {
animation: shrink-indicator linear both;
animation-timeline: --hero-view;
animation-range: 0% 100%;
}
7. Fallback mit @supports und IntersectionObserver
Scroll-Timelines sind in der Support-Matrix aktuell weniger verbreitet als grundlegende CSS-Animationen. Für Projekte, die auf breitere Browser-Unterstützung angewiesen sind, empfiehlt sich eine klare Trennung über @supports (animation-timeline: view());. Innerhalb dieses Blocks stehen alle Scroll-Timeline-Deklarationen, außerhalb greift ein einfacher IntersectionObserver, der dieselbe CSS-Klasse toggelt, aber ohne Scrubbing-Fähigkeit.
Wichtig ist, dass beide Codepfade dasselbe visuelle Grundgerüst teilen, damit kein Bruch im Erscheinungsbild entsteht. In der Praxis bedeutet das: Start- und Endzustand der Animation werden als reguläre CSS-Klassen definiert, und sowohl die Scroll-Timeline-Variante als auch der IntersectionObserver-Fallback schalten lediglich zwischen diesen Klassen um, statt komplett unterschiedliche Animationslogik zu pflegen.
// Fallback only runs when native scroll-driven animations are unsupported
if (!CSS.supports('animation-timeline: view()')) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
entry.target.classList.toggle('is-revealed', entry.isIntersecting);
});
}, { threshold: 0.4 });
document.querySelectorAll('.story-card').forEach((card) => {
observer.observe(card);
});
}
8. Debugging von Scroll-Timelines in den DevTools
Fehlerhafte Scroll-Timeline-Setups zeigen sich meist auf eine bestimmte Art: die Animation läuft überhaupt nicht, oder sie läuft nur einmal statt scrubbing-fähig zu bleiben. Die häufigste Ursache ist ein fehlender overflow-Wert auf dem Timeline-Quellelement, gefolgt von einem Tippfehler im Custom-Ident-Namen zwischen Quelle und Ziel. Chromium-basierte DevTools zeigen im Element-Panel unter "Animations" aktive Scroll-Timelines mit ihrem aktuellen Fortschritt an, inklusive eines visuellen Scrubbers.
Ein zweiter typischer Fehler betrifft Timing-Funktionen: eine Scroll-Timeline-Animation mit ease-in-out statt linear wirkt bei genauem Hinsehen ungleichmäßig, weil die Easing-Kurve für Zeit-basierte Animationen gedacht ist, nicht für Scroll-Fortschritt-basierte. Für die meisten Scroll-Timeline-Fälle ist linear die richtige Timing-Funktion, die eigentliche Beschleunigung oder Verzögerung entsteht über die Keyframe-Verteilung selbst, nicht über die Easing-Funktion.
9. Scroll-Timeline-Patterns im Vergleich
Für die Wahl der richtigen Technik lohnt sich ein direkter Vergleich der gängigen Ansätze für scroll-gekoppelte Animationen, von der einfachen anonymen Timeline bis zum vollständig choreografierten Multi-Timeline-Setup.
| Pattern | Kontrolle | JavaScript nötig | Einsatzbereich |
|---|---|---|---|
| Anonyme scroll()-Timeline | Gering | Nein | Einfacher Fortschrittsbalken |
| Named scroll-timeline | Mittel | Nein | Element außerhalb des Containers ansteuern |
| view-timeline + animation-range | Hoch | Nein | Präzise choreografierte Reveal-Effekte |
| timeline-scope | Hoch | Nein | Cross-Element-Scrollytelling |
| IntersectionObserver | Gering, kein Scrubbing | Ja | Fallback für ältere Browser |
Für neue Projekte, die moderne Browser priorisieren können, ist view-timeline mit präzisem animation-range der klare Favorit: maximale Kontrolle, keine Laufzeitkosten für den Main-Thread, keine zusätzliche JavaScript-Abhängigkeit. Nur wenn breite Legacy-Unterstützung Pflicht ist, bleibt IntersectionObserver als robuster, aber weniger flüssiger Fallback relevant.
Mironsoft
Scrollytelling, Parallax und performante Animationen für moderne Frontends
Scroll-Effekte, die ohne JavaScript flüssig laufen?
Wir bauen präzise choreografierte Scroll-Timeline-Animationen mit view-timeline und animation-range, inklusive Fallback-Strategie für ältere Browser und vollständigem Performance-Audit.
Konzeption
Scrollytelling-Layouts planen und Timeline-Struktur entwerfen
Umsetzung
Named Timelines, view-timeline und präzises animation-range
Qualitätssicherung
Fallback-Tests, Compositor-Performance und Debugging-Setup
10. Zusammenfassung
Fortgeschrittene Scroll-Timeline-Technik geht deutlich über ein einzelnes Fade-in beim Scrollen hinaus. scroll-timeline-name entkoppelt Quelle und Ziel einer Timeline, view-timeline koppelt eine Animation an die Sichtbarkeit eines Elements statt an einen Container, und animation-range mit den Werten entry, exit, cover und contain erlaubt eine präzise Choreografie einzelner Animationsphasen.
Mehrere gleichzeitig aktive Timelines ermöglichen vollständig deklarative Parallax-Effekte mit unterschiedlichen Geschwindigkeiten pro Ebene, timeline-scope löst das Problem von Steuerelementen außerhalb des eigenen Subtrees. Da all diese Effekte auf dem Compositor-Thread laufen, bleibt die Scroll-Performance auch bei komplexen Setups stabil, ganz ohne scroll-Event-Listener und ohne die damit verbundenen Main-Thread-Kosten. Ein @supports-Fallback mit IntersectionObserver sichert die Funktion auf Browsern ohne Unterstützung ab.
Scroll-Timeline fortgeschritten — Das Wichtigste auf einen Blick
Named Timelines
scroll-timeline-name entkoppelt Quelle und animiertes Ziel, unabhängig von der DOM-Position.
View-Timeline
view-timeline koppelt an die Sichtbarkeit eines Elements, ideal für Reveal-Effekte und Scrollytelling.
Präzises animation-range
entry, exit, cover, contain erlauben feine Choreografie statt Alles-oder-nichts-Fades.
Performance
Compositor-Thread statt Scroll-Event-Listener. Kein Layout-Thrash, kein Main-Thread-Overhead.