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.

  • @keyframes beschreibt, was passiert
  • animation-timeline bestimmt, 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:

  • @keyframes definiert was passiert
  • animation + animation-timeline definieren 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.

Reveal 1

Startet beim Reinkommen.

Reveal 2

Kein Pin, kein Scroll-Scrubbing.

Reveal 3

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.

Variante A · Prozent
20% → 80%

Startet erst bei 20% der Timeline und ist bei 80% fertig.

Variante B · Keywords
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-range ist 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-motion fü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.