Scroll-driven Animations
Bei Scroll-driven Animations wird der Fortschritt einer CSS-Animation nicht durch Zeit, sondern durch die Scrollposition gesteuert. Die Animation kann dabei dem Scrollfortschritt der Seite oder der Position eines Elements im Viewport folgen.
Zentrale Begriffe
animation-timelineVerbindet den Fortschritt einer Animation mit einer Timeline.scroll()Die Timeline folgt dem Scrollfortschritt eines Scroll-Containers.view()Die Timeline folgt der Position eines Elements im Viewport.animation-rangeBestimmt, welcher Abschnitt der Timeline für die Animation verwendet wird.
1. animation-timeline – Timeline zuweisen
animation-timeline bestimmt, welche Timeline den Fortschritt einer Animation
steuert. Statt der verstrichenen Zeit kann die Scrollposition der Seite oder die Position eines
Elements im Viewport verwendet werden.
animation-timeline: scroll(root);
animation-timeline: view();
animation-timeline wird nach der Kurzschreibweise animation notiert,
weil diese den Wert sonst wieder auf den Standard zurücksetzt.
.element {
animation: fadeUp linear both;
animation-timeline: view();
}
2. scroll() – Scroll-Container wählen
scroll() koppelt den Fortschritt einer Animation an die Scrollposition eines
Scroll-Containers. Ohne Angabe verwendet der Browser den nächsten scrollbaren Vorfahren und die
Block-Achse.
scroll()Verwendet standardmässig den nächsten scrollbaren Vorfahrenscroll(root)Verwendet den Scrollfortschritt der gesamten Seitescroll(nearest)Wählt ausdrücklich den nächsten scrollbaren Vorfahrenscroll(self)Verwendet die Scrollposition des Elements selbst
animation-timeline: scroll();
animation-timeline: scroll(root);
animation-timeline: scroll(nearest);
animation-timeline: scroll(self);
scroll() und scroll(nearest) bewirken dasselbe.
scroll(self) funktioniert nur, wenn das Element selbst ein Scroll-Container ist.
scroll(root) eignet sich besonders für seitenweite Effekte wie Progress-Bars oder
Parallax-Animationen. Für Animationen einzelner Elemente ist view() meist besser
geeignet.
Scrolle in dieser Box
- Das Hintergrundbild folgt dem Scrollfortschritt dieses Containers.
- Je weiter du innerhalb der Box scrollst, desto weiter verschiebt sich das Bild.
- Die Animation wird nicht durch Zeit gesteuert.
- Der Scroll-Container steuert die Position seines eigenen Hintergrundbildes.
- Am Ende des Scrollbereichs ist auch die Animation am Ende.
The quick brown fox jumps over the lazy dog
Der Satz ist so bekannt, weil es sich um ein Pangramm handelt – ein Satz, der jeden
einzelnen Buchstaben des englischen Alphabets (von A bis Z) mindestens einmal enthält.
Deshalb wird er seit Jahrzehnten genutzt, um Schriftarten zu testen oder das Tippen auf
Tastaturen zu üben.
.scroll-demo {
overflow-y: auto;
background-position: right top;
animation: move-background linear both;
/* Das Element verwendet seinen eigenen Scrollfortschritt */
animation-timeline: scroll(self);
}
@keyframes move-background {
to {
background-position: left bottom;
}
}
3. view() – Element im Viewport
view() koppelt den Fortschritt der Animation an die Position eines Elements im
Viewport. Die Animation verändert sich, während das Element hinein-, hindurch- oder
hinausscrollt.
.view-demo-box {
animation: appear linear both;
/* Die Position der Box im Viewport steuert die Animation */
animation-timeline: view();
/* Die Animation läuft vom Eintritt bis zur Mitte */
animation-range: entry 0% cover 50%;
}
@keyframes appear {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
4. animation-range – Bereich festlegen
animation-range bestimmt, welcher Abschnitt der Timeline den Keyframes von
0 % bis 100 % entspricht. So lässt sich festlegen, wann eine Animation beginnt und
wann sie abgeschlossen ist.
Range-Werte
normalVerwendet den gesamten Standardbereich der Timeline.20% 80%Die Animation läuft zwischen 20 % und 80 % der Timeline.entryDas Element kommt in den Viewport hinein.exitDas Element verlässt den Viewport.coverDas Element bewegt sich durch den sichtbaren Bereich.containDas Element befindet sich vollständig im Viewport.
Syntax-Muster
animation-range: entry 20% cover 50%;
Die Angabe kombiniert einen Range-Namen mit einem Offset, meist in Prozent.
Aufbau einer Scroll-Animation
Eine Scroll-driven Animation verbindet klassische Keyframes mit einer scrollbasierten Timeline.
@keyframesdefiniert, was passiertanimationweist die Keyframes dem Element zuanimation-timelinebestimmt, wodurch der Fortschritt gesteuert wirdanimation-rangebegrenzt optional den verwendeten Abschnitt der Timeline
@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% cover 50%;
}
In diesem Beispiel folgt die Animation der Position der Karte im Viewport. Sie beginnt, wenn die
Karte in den Viewport eintritt, und ist in der Mitte des cover-Bereichs
abgeschlossen.
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); }
}
}
Bewegung reduzieren
@media (prefers-reduced-motion: reduce) {
.progress {
display: none;
}
}
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(root)) {
.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.
Die Position der Karte steuert die Animation.
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.
Kernidee
@supports (animation-timeline: view()) {
.reveal {
opacity: 0;
transform: translateY(60px) scale(.9);
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 50%;
}
@keyframes reveal {
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
}
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: pop linear both;
animation-timeline: view();
}
.rangeBox[data-range="percent"] { animation-range: 20% 80%; }
.rangeBox[data-range="keywords"] { animation-range: entry 20% cover 50%; }
}
5. Bento Grid mit Scroll-Animation
Dieses Beispiel kombiniert view(), animation-range, Custom Properties
und ein responsives Bento Grid. Bento
Grid mit Scroll-Animation auf CodePen ansehen.
6. Elemente staffeln
Sollen mehrere Elemente nicht gleichzeitig, sondern nacheinander erscheinen – z. B.
Listeneinträge, Karten oder Galerie-Bilder – reicht eine einzige @keyframes-Regel
für alle. Die Staffelung entsteht über ein versetztes animation-delay oder einen
Range-Offset pro Element.
Klassisch: :nth-child()
Jedes Element bekommt über :nth-child() (siehe Pseudoklassen) ein eigenes Delay.
Funktioniert überall, muss aber für jede Position einzeln geschrieben werden. Ein Beispiel für
reines Staffeln beim
Laden, ohne Scroll-Trigger, gibt es in dieser Animated
Gallery auf CodePen.
Diese Demo startet erst, wenn du bis hierhin scrollst – nicht beim Laden der
Seite. Sie kombiniert zwei Techniken: den Range-Offset pro Element für die Staffelung
(:nth-child()) mit einer scrollbasierten Timeline, die den Start an die
Sichtbarkeit koppelt statt an die Ladezeit (animation-timeline: view()).
Hinweis: Lädst du die Seite neu, während dieser Abschnitt schon im Sichtbereich
ist, spielt die Animation nicht nochmal ab – view() zeigt immer den Zustand, der
zur aktuellen Scrollposition passt, statt jedes Mal von vorne zu starten. Kein Bug, das ist der
Unterschied zu einer zeitbasierten Animation.
1. Animation definieren
@keyframes staggerIn {
from {
opacity: 0;
transform: translateY(70px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
2. Animation zuweisen
.stagger-demo .samp {
animation: staggerIn 0.6s ease forwards;
}
3. Staffelung über Sekunden-Delay
/* Fallback ohne view()-Support: Staffelung über Sekunden-Delay */
@supports not (animation-timeline: view()) {
.stagger-demo .samp:nth-child(1) { animation-delay: 0s; }
.stagger-demo .samp:nth-child(2) { animation-delay: 0.25s; }
.stagger-demo .samp:nth-child(3) { animation-delay: 0.5s; }
.stagger-demo .samp:nth-child(4) { animation-delay: 0.75s; }
}
4. Staffelung über Range-Offset
/* Start an Sichtbarkeit statt an Ladezeit koppeln, Staffelung über Range-Offset */
@supports (animation-timeline: view()) {
.stagger-demo .samp {
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
.stagger-demo .samp:nth-child(2) { animation-range: entry 5% cover 60%; }
.stagger-demo .samp:nth-child(3) { animation-range: entry 10% cover 80%; }
.stagger-demo .samp:nth-child(4) { animation-range: entry 15% cover 100%; }
}
sibling-index() – Bleeding Edge
Statt jede Position manuell zu adressieren, liefert sibling-index() die Position
eines Elements unter seinen Geschwistern direkt als Zahl – nutzbar in calc(). Eine
einzige Regel reicht für beliebig viele Elemente, auch wenn später mehr dazukommen.
.stagger-demo .samp {
animation: staggerIn 0.6s ease forwards;
animation-delay: calc((sibling-index() - 1) * 0.15s);
}
Browser-Support (Stand 2026): sibling-index() ist seit August 2026
Baseline und funktioniert in den aktuellen Browsern.
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 */
}
Reduzierte Bewegung
prefers-reduced-motion reagiert auf die Einstellungen der Nutzer:innen und ist
wichtig für barrierearme Animationen. Scroll-Animationen sollten die Einstellung
prefers-reduced-motion berücksichtigen.
@media (prefers-reduced-motion: reduce) {
.pop {
animation: none;
opacity: 1;
transform: none;
}
.progress {
display: none;
}
}
Performance
Für flüssige Scroll-Animationen eignen sich besonders transform und
opacity. Diese Eigenschaften verändern den Dokumentenfluss nicht und können vom
Browser meist effizient dargestellt werden.
Animationen von Layout-Eigenschaften wie top, left, width
oder height können aufwendiger sein. Scroll-Effekte sollten zudem zurückhaltend
eingesetzt werden, damit die Seite ruhig und gut bedienbar bleibt.
Kurz gesagt
- Scroll-driven Animations werden durch Scrollen statt durch Zeit gesteuert.
scroll(root)folgt der Scrollposition der Seite.view()folgt der Sichtbarkeit eines Elements.animation-rangebestimmt Start und Ende innerhalb der Timeline.@supportsundprefers-reduced-motionsorgen für Fallback und reduzierte Bewegung.
Verwandte Themen
- Animation – Abläufe mit Keyframes und Animation-Eigenschaften gestalten.
- Transform – Elemente verschieben, drehen, skalieren oder neigen.
- Transition – Zustandsänderungen fliessend darstellen.
- CSS Scroll-Properties – Scrollverhalten, Snap-Punkte und Abstände steuern.