CSS-Animationen beim Scrollen auslösen – ganz ohne JavaScript
Erstellen Sie scrollgesteuerte CSS-Animationen ohne JavaScript. Erfahren Sie mehr über Syntax, Trigger-Aktionen, Scroll-Grenzen und IntersectionObserver-Fallbacks.
Bei scroll-getriggerten Animationen entscheidet das Scrollen, wann eine normale CSS-Animation startet. Sobald ein Element eine festgelegte Scrollposition erreicht, läuft die Animation nach ihrer eigenen Zeitsteuerung ab. Deklariert werden sie über die Properties timeline-trigger und animation-trigger, ein IntersectionObserver ist nicht nötig.
Der meiste „Fade-in beim Scrollen“-Code sieht überall gleich aus: ein Observer, ein Schwellenwert, ein Klassen-Toggle und ein unobserve()-Aufruf, den irgendjemand vergisst. Diese Arbeit lässt sich jetzt in CSS erledigen.
Dieser Artikel behandelt die Syntax, ein vollständiges Reveal-Beispiel, die drei Trigger-Verhaltensweisen, die Wahl der Scroll-Grenzen und den sicheren Einsatz des Features in einer Welt, in der es nur Chromium unterstützt.
Das Wichtigste in Kürze
- Eine scroll-getriggerte Animation benötigt zwei Deklarationen.
timeline-triggerbenennt einen Trigger und legt den Scrollbereich fest, der ihn aktiviert.animation-triggerteilt einer Animation mit, welchem Trigger sie folgen soll und was beim Auslösen geschehen soll. - Der Bereich
entry 100% exit 0%aktiviert einen Trigger nur, solange sich das Element vollständig innerhalb des Scrollports befindet. Die Chrome-Dokumentation behandelt ihn als gleichwertig zum Schlüsselwortcontain. play-forwards play-backwardsspielt die Animation beim Eintritt ab und kehrt sie beim Austritt um.play-onceaus dem Animation-Triggers-Entwurf führt sie genau einmal aus.- Eine getriggerte Animation behält ihre eigene Dauer und ihr eigenes Easing – schnelleres Scrollen lässt sie also nicht schneller ablaufen.
- Laut Chromes Support-Daten unterstützen nur Chromium-Browser scroll-getriggerte Animationen. Verstecken Sie Inhalte daher nur innerhalb von
@supportsund behalten Sie einen IntersectionObserver-Fallback bei.
Was ist der Unterschied zwischen scroll-gesteuerten und scroll-getriggerten Animationen?
Scroll-gesteuerte Animationen (Scroll-driven Animations) koppeln den Fortschritt einer Animation an die Scrollposition. Scroll-getriggerte Animationen nutzen das Scrollen nur, um zu entscheiden, wann eine gewöhnliche Animation startet. Unser Leitfaden, wie Sie Scroll-Animationen allein mit CSS erstellen, behandelt das erste Modell: animation-timeline mit scroll() und view(). Dort ersetzt die Timeline die Uhr, sodass Zurückscrollen die Animation rückwärts abspielt. Dieser Artikel behandelt timeline-trigger und animation-trigger, die eine reguläre Animation mit eigener Dauer starten, sobald das Element einen gewählten Scrollbereich erreicht.
Für einen Lesefortschrittsbalken oder Parallax-Effekte eignen sich CSS-Scroll-Progress-Animationen. Soll eine Card über 400 ms eingeblendet werden, sobald sie sichtbar ist, verwenden Sie einen Trigger. In seiner Web-UI-Übersicht zur Google I/O 2026 führt Chrome beides als separate Features auf und beschreibt Trigger als Möglichkeit, IntersectionObserver durch CSS zu ersetzen. Vereinfacht gesagt ist ein Trigger die CSS-Version von „füge eine Klasse hinzu, sobald dieses Element sichtbar wird“.
Ein Beispiel für ein scroll-getriggertes Fade-in
Ein scroll-getriggertes Fade-in besteht aus drei Teilen: einer @keyframes-Regel, einer animation mit forwards-Fill und einem Trigger, der die Animation mit einer View-Timeline verknüpft. Die Chrome-Ankündigung zu scroll-getriggerten Animationen definiert die Syntax. Die Kernpunkte: Trigger und Animation können auf unterschiedlichen Elementen sitzen, und ein Trigger nutzt eine Scroll- oder View-Progress-Timeline als Quelle.
<section class="reveal">
<div class="reveal-content">
<h2>Shipping notes</h2>
<p>This block fades and slides in once it is fully on screen.</p>
</div>
</section>
@keyframes reveal {
to {
opacity: 1;
translate: 0 0;
}
}
@supports (timeline-trigger-name: --t) {
@media (prefers-reduced-motion: no-preference) {
.reveal {
timeline-trigger: --reveal view() entry 100% exit 0%;
trigger-scope: --reveal;
}
.reveal-content {
opacity: 0;
translate: 0 2rem;
animation: reveal 400ms ease-out forwards;
animation-trigger: --reveal play-forwards;
}
}
}
So funktioniert es:
- Der Trigger sitzt auf der Section, die Bewegung auf ihrem Kindelement. Das Element, das hineingleitet, ist nicht dasjenige, dessen Sichtbarkeit gemessen wird – der
translate-Versatz kann die Trigger-Grenze also nicht verschieben. entry 100% exit 0%aktiviert den Trigger, sobald sich die Section vollständig innerhalb des Scrollports befindet.play-forwardsstartet die Animation bei jeder Aktivierung des Triggers. Derforwards-Fill hält anschließend den letzten Keyframe.- Der versteckte Ausgangszustand steht innerhalb der Feature-Query. Browser ohne Unterstützung wenden
opacity: 0nie an, der Inhalt bleibt also sichtbar. trigger-scopehält den Namen--revealjeder Section lokal. Standardmäßig kann jedes Element auf der Seite einen Trigger-Namen sehen – genau wie beianchor-name, wie die Release Notes zu Chrome 146 beschreiben.
Mit play-forwards allein passiert beim Verlassen des Bereichs nichts: Das Element behält beim Wegscrollen seinen Endzustand. Wird es wieder vollständig sichtbar, aktiviert sich der Trigger erneut und die Animation wird noch einmal abgespielt. Im nächsten Abschnitt sehen Sie, wie Sie das ändern.
Die passende Trigger-Aktion wählen
animation-trigger akzeptiert einen Trigger-Namen, gefolgt von bis zu zwei Aktionen. Die erste wird ausgeführt, wenn der Trigger aktiviert wird, die zweite, wenn er deaktiviert wird. Die gewählte Aktion bestimmt, was beim erneuten Eintritt und beim Austritt passiert.
| Gewünschtes Verhalten | Wert für animation-trigger | Hinweis |
|---|---|---|
| Bei jedem erneuten Eintritt wiederholen | --reveal play-forwards | Kann sichtbar in den Ausgangszustand zurückspringen |
| Beim Austritt umkehren | --reveal play-forwards play-backwards | Nach der Umkehrung zeigt das Element seine Basis-Styles |
| Einmal ausführen, dann stehen bleiben | --reveal play-once | Schlüsselwort aus dem Entwurf, kann sich ändern |
animation-trigger: --reveal play-forwards play-backwards; /* reverses on exit */
animation-trigger: --reveal play-once; /* runs once; Animation Triggers draft keyword */
Der forwards-Fill hält den to-Keyframe, nachdem play-forwards beendet ist. Nach dem Ende von play-backwards hält er dagegen nichts. Die Animation wirkt dann nicht mehr, und das Element fällt auf seine Basis-Styles zurück. In diesem Beispiel sind die Basis-Styles der versteckte Ausgangszustand (opacity: 0; translate: 0 2rem), da die Keyframes nur to definieren – das Element ist am Ende also trotzdem versteckt. Legen Ihre Keyframes einen from-Zustand fest, der von den Basis-Styles abweicht, verwenden Sie both, damit dieser Zustand nach der Umkehrung gehalten wird. play-once stammt aus dem CSS Animation Triggers Level 1 Editor’s Draft, nicht aus Chromes Ankündigung. Prüfen Sie es daher in Ihrem Ziel-Build von Chrome, bevor Sie sich darauf verlassen. Es entspricht am ehesten einem unobserve()-Aufruf nach der ersten Überschneidung.
Aktivierungsbereich vs. aktiver Bereich
Bei einem Scroll-Trigger bestimmt der Aktivierungsbereich, wann der Trigger auslöst. Der optionale aktive Bereich, der nach einem Schrägstrich angegeben wird, bestimmt, wie lange der Trigger aktiv bleibt – Chrome verlangt, dass er den Aktivierungsbereich umschließt. Laut Chrome entspricht entry 100% exit 0% dem Wert contain und entry 0% exit 100% dem Wert cover. Diese beiden Zeilen sind also identisch:
timeline-trigger: --reveal view() entry 100% exit 0% / entry 0% exit 100%;
timeline-trigger: --reveal view() contain / cover;
Mit dieser Einstellung schaltet sich der Trigger ein, sobald das Element vollständig sichtbar ist, und bleibt aktiv, bis kein Teil davon mehr sichtbar ist. Das ist für play-backwards relevant, denn die umgekehrte Animation wartet, bis das Element den Bildschirm verlassen hat.
Sinnvolle Scroll-Grenzen festlegen
Der richtige Bereich für einen Scroll-Trigger hängt von der Größe des Elements und von fixierten UI-Elementen ab, die den Viewport überdecken.
| Situation | Bereich | Begründung |
|---|---|---|
| Kleine Card oder Überschrift | entry 100% exit 0% (contain) | Löst aus, sobald das gesamte Element sichtbar ist |
| Hohe Section | entry 25% exit 0% | contain kann bei hohen Elementen zu spät auslösen |
| Unter einem 4rem hohen Sticky Header | view(block 4rem 0) | Verhindert, dass der Header das Element verdeckt |
/* Tall element: fire when a quarter has entered */
timeline-trigger: --reveal view() entry 25% exit 0%;
/* 4rem sticky header: start (top) inset first */
timeline-trigger: --reveal view(block 4rem 0) entry 100% exit 0%;
Bei view() steht das Start-Inset vor dem End-Inset. Auf einer vertikalen Block-Achse ist der Start oben, daher kommt die Höhe eines oben platzierten Headers zuerst.
Browser-Support und Fallbacks
Chromes Web-UI-Übersicht zur Google I/O 2026 nennt Unterstützung für scroll-getriggerte Animationen in Chrome und Edge 146, Firefox und Safari unterstützen sie nicht. Chromes Ankündigung vom Dezember 2025 sah das Feature für Chrome 145 vor, ausgeliefert wurde es jedoch mit Chrome 146 (Stable-Release am 10. März 2026). Der Beitrag zur Beta von Chrome 146 führt trigger-scope im selben Release auf. Behandeln Sie das gesamte Feature als Progressive Enhancement.
Der @supports-Wrapper im Beispiel übernimmt genau das. Nicht unterstützende Browser überspringen den gesamten Block und rendern den Inhalt statisch. Möchten Sie dieselbe Bewegung auch in anderen Browsern, behalten Sie Ihren bestehenden IntersectionObserver-Klassen-Toggle außerhalb der Feature-Query bei und führen Sie ihn nur aus, wenn CSS.supports() false zurückgibt. Unser Beitrag zum Ersetzen von Animationsbibliotheken durch native Web-APIs zeigt, wie Sie diesen Fallback ohne Abhängigkeiten im Stil von AOS umsetzen.
Die Query prefers-reduced-motion: no-preference sorgt dafür, dass Nutzer, die weniger Bewegung wünschen, Inhalte sofort sehen. Um mehrere Elemente gestaffelt zu animieren, verweisen Sie mit ihrem animation-trigger auf einen gemeinsamen Trigger und vergeben unterschiedliche Delays.
Fazit
Scroll-getriggerte Animationen decken das Fade-in-beim-Scrollen-Muster ab: CSS entscheidet, wann eine gewöhnliche Animation startet, und der Browser übernimmt die Beobachtung. Beginnen Sie mit dem obigen Beispiel. Wählen Sie die Aktion (wiederholen, umkehren oder einmal ausführen), die zu Ihrem Design passt, passen Sie den Bereich für hohe Elemente oder Sticky Header an und entfernen Sie den Observer-Code für unterstützende Browser, während er für alle anderen erhalten bleibt.
FAQs
Was ist der Unterschied zwischen scroll-getriggerten Animationen und Scroll-State Container Queries?
Scroll-State Container Queries wenden Styles an, solange sich ein Container in einem vom Browser verwalteten Scroll-Zustand befindet. Scroll-getriggerte Animationen starten eine Keyframe-Animation, wenn ein Element einen Bereich auf einer View- oder Scroll-Timeline durchläuft. Chrome 133 hat die Zustände stuck, snapped und scrollable eingeführt, Chrome 144 hat scrolled ergänzt, das die zuletzt verwendete Scrollrichtung erfasst. Verwenden Sie eine Scroll-State Query für einen Sticky Header, der sein Styling ändert, sobald er fixiert ist, und einen Trigger für Reveal-Effekte.
Sind scroll-getriggerte Animationen schneller als ein Reveal per IntersectionObserver?
Laut Chromes Release Notes kann der Browser die Arbeit an einen Worker-Thread übergeben, da die Interaktion in CSS beschrieben ist. Ein Reveal per IntersectionObserver führt Callback und Klassen-Toggle auf dem Main Thread aus, sodass aufwendige Skriptarbeit ihn verzögern kann. Chrome beschreibt die Auslagerung als konzeptionellen Vorteil, nicht als gemessenes Ergebnis. Profilen Sie Ihre eigenen Seiten daher vor und nach der Umstellung, statt von einem Gewinn auszugehen.
Können Scroll-Trigger Animationen steuern, die mit der Web Animations API erstellt wurden?
Ja. Laut den Release Notes zu Chrome 146 bringt das Feature JavaScript-Schnittstellen mit, sodass Trigger nicht nur CSS-Animationen, sondern auch mit der Web Animations API erstellte Animationen steuern können. Die Namen der Schnittstellen sind im CSS Animation Triggers Level 1 Editor's Draft definiert, der sich noch ändern kann. Prüfen Sie daher den Entwurf und Ihren Ziel-Build von Chrome, bevor Sie sich darauf verlassen.
Warum bleibt mein Element nach dem Hinzufügen eines Scroll-Triggers versteckt?
Der versteckte Ausgangszustand greift, sobald die Feature-Query zutrifft, das Element bleibt also bei opacity 0, bis sein Trigger aktiviert wird. Häufige Ursachen sind ein Schreibfehler, durch den der Name in timeline-trigger nicht mit dem in animation-trigger übereinstimmt, ein animiertes Element außerhalb des durch trigger-scope festgelegten Teilbaums oder ein Bereich, den das Element nie erreicht. Prüfen Sie zuerst den Namen und stellen Sie dann sicher, dass sich das animierte Element innerhalb des Scoping-Elements befindet.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k