Scroll-driven Animations
Bei Scroll-driven Animations wird der Fortschritt einer CSS-Animation durch Scrollen statt durch Zeit gesteuert.
Grundidee
Klassische CSS-Animationen sind zeitbasiert. Scroll-driven Animations sind scrollbasiert: Die Animation läuft nur weiter, wenn der Nutzer scrollt.
@keyframesbeschreibt, was passiertanimation-timelinebestimmt, wodurch es voranschreitet (Scroll statt Zeit)
Scroll-Timeline vs. View-Timeline
Statt animation-duration: 2s steuerst du den Fortschritt über Scroll. Das hat zwei typische Modi:
scroll(root)→ Fortschritt folgt dem Seiten-Scroll.view()→ Fortschritt folgt der Sichtbarkeit eines Elements.
Syntax
Du brauchst – wie bei klassischen Animationen – zwei Teile:
@keyframesdefiniert was passiertanimation+animation-timelinedefinieren wodurch der Fortschritt läuft (Scroll statt Zeit)
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fadeUp linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
Optional: Mit animation-range definierst du,
welcher Abschnitt der Timeline von 0–100% für die Animation verwendet wird.
Animationen Beispiele
1. Progress Bar
HTML
<div class="progress"></div>
CSS - Look
.progress {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 4px;
transform-origin: left;
transform: scaleX(0);
background: linear-gradient(45deg, cyan, yellow, red, indigo, cyan);
z-index: 9999;
}
CSS - Animation
@supports (animation-timeline: scroll(root)) {
.progress{
animation: grow linear both;
animation-timeline: scroll(root);
}
@keyframes grow{
to { transform: scaleX(1); }
}
}
Erklärung: Oben ist eine Linie, die von 0 → 100% wächst, während du scrollst.
Tipp: Für Progress-Bars ist transform: scaleX() sehr performant (GPU-freundlich).
2. Pin-Animationen (scroll(root))
In dieser Demo bleibt jede Box kurz „gepinnt“ (via position: sticky). Während du weiter scrollst, scrubbt die Animation entlang der Seiten-Scrollposition (scroll(root)).
See the Pen Scroll-Timeline by Intensivstation (@intensivstation) on CodePen.
Erklärung
- Alle Boxen hängen an derselben Scroll-Timeline (
scroll(root)). - Der Scroll ersetzt die Zeit: 0% → 100% entspricht einer Scroll-Strecke.
- Der sichtbare Unterschied entsteht nur durch unterschiedliche
@keyframes.
Kernidee
.pin { position: sticky; top: 20px; }
@supports (animation-timeline: scroll()) {
.box{
animation: effect linear both;
animation-timeline: scroll(root);
animation-range: 0% 25%;
}
}
Merksatz
scroll(root)= Timeline folgt der Scrollposition der Seite.view()= Timeline folgt der Sichtbarkeit eines Elements.
3. View-basierte Reveal-Animation
Bei view() wird der Fortschritt der Animation an die Sichtbarkeit eines Elements im Viewport gekoppelt. Je weiter das Element eintritt, desto weiter läuft die Animation.
Startet beim Reinkommen.
Kein Pin, kein Scroll-Scrubbing.
Sichtbarkeit steuert die Animation.
Erklärung
- Die Animation startet, wenn das Element in den Viewport kommt.
view()koppelt die Animation an Sichtbarkeit.- Ausserhalb des Viewports steht die Animation still.
Hinweis:
Visuell kann view() ähnlich wirken wie scroll(root).
Der Unterschied zeigt sich im Verhalten:
view() reagiert auf Sichtbarkeit, scroll(root) auf die Seiten-Scrollposition.
4. Animation-range vergleichen
animation-range legt fest, welcher Abschnitt einer Timeline für 0% → 100% der Animation verwendet wird.
Hier siehst du den gleichen Effekt, aber mit zwei unterschiedlichen Ranges.
So testest du es: Scroll langsam und achte darauf, wann die Boxen beginnen und wann sie „fertig“ sind.
20% → 80%Startet erst bei 20% der Timeline und ist bei 80% fertig.
entry 20% → cover 50%Bezieht sich auf Sichtbarkeits-Phasen des Elements (entry/cover).
Erklärung
- Prozent (
20% → 80%) bezieht sich direkt auf die Timeline (0–100%). - Keywords (
entry…cover…) beziehen sich auf Phasen der Sichtbarkeit im Viewport. - Wenn ein Effekt „zu früh“ startet oder „zu spät“ endet:
animation-rangeist fast immer der Hebel.
Kernidee
@supports (animation-timeline: view()) {
.rangeBox { animation-timeline: view(); animation: pop linear both; }
.rangeBox[data-range="percent"] { animation-range: 20% 80%; }
.rangeBox[data-range="keywords"] { animation-range: entry 20% cover 50%; }
}
animation-range – der wichtigste Hebel
animation-range definiert, welcher Abschnitt der Timeline
der Animation von 0 → 100 % entspricht.
Wenn eine Scroll-Animation zu früh oder zu spät wirkt, ist fast immer animation-range der richtige Hebel.
Wichtig: Der Startzustand (transform: scale(.92), opacity: .35) steht direkt am Element. Das Keyframe beschreibt nur, wohin es am Ende gehen soll.
Range-Werte (Spickzettel)
normalStandardbereich der jeweiligen Timeline.20% 80%Prozent-Range: Animation läuft nur in diesem Abschnitt.entryElement kommt in den Viewport hinein.exitElement verlässt den Viewport.coverGesamter Sichtbarkeitsbereich.containElement ist vollständig im Viewport.
Syntax-Muster
animation-range: entry 20% cover 50%;
Range-Name + Offset (meist Prozent).
Fallback
Progressive Enhancement
Scroll-Timelines sind modern. Deshalb: Feature testen und nur dann aktivieren.
@supports (animation-timeline: view()) {
/* moderne scroll-driven Version */
}
@supports not (animation-timeline: view()) {
/* Fallback: statische Darstellung oder normale Transition */
}
Performance & Best Practices
- Bevorzugt animieren:
transform,opacity(GPU-freundlich) - Vermeiden: Layout-Properties wie
top,left,width,height - Berücksichtige
prefers-reduced-motionfür barrierearme Interfaces - Halte Effekte subtil: Scroll-Animationen wirken schnell „busy“, wenn sie zu stark sind
Cheat-Sheet
scroll(root)Timeline folgt dem Seiten-Scroll (gut für Progress-Bars).view()Timeline folgt der Sichtbarkeit eines Elements (Reveal-Effekte).animation-timelineHängt Animation an Timeline (Scroll statt Zeit).animation-rangeStart/Ende der Animation innerhalb der Timeline.