Programmatische Animationen jenseits der Grenzen von CSS-Transitions
Wenn Animationen von Laufzeitwerten abhaengen, unterbrechbar sein muessen oder sich mit anderen Animationen synchronisieren sollen, stoesst reines CSS an Grenzen. Die Web Animations API loest genau das, mit derselben Compositor-Performance wie native CSS-Animationen und voller programmatischer Kontrolle ueber jede laufende Animation.
Inhaltsverzeichnis
- 1. Wo CSS-Animationen an ihre Grenzen stossen
- 2. element.animate(): Grundlagen und Syntax
- 3. Das Animation-Objekt steuern
- 4. Zur Laufzeit berechnete Keyframes
- 5. Timelines und Synchronisation mehrerer Animationen
- 6. Das finished-Promise und Animationsketten
- 7. Performance: Compositor-Thread statt Hauptthread
- 8. Wann CSS, wann WAAPI?
- 9. Best Practices und Zusammenfassung
- 10. Zusammenfassung
- 11. FAQ
1. Wo CSS-Animationen an ihre Grenzen stossen
CSS-Transitions und -Keyframe-Animationen decken die meisten UI-Animationen gut ab: Hover-Effekte, einfache Ein- und Ausblendungen, feste Ladeanimationen. Sobald Werte aber erst zur Laufzeit berechnet werden muessen, etwa die Zielposition eines Drag-and-Drop-Elements, die Restdistanz eines Scroll-Fortschritts oder ein physikbasierter Bounce-Effekt, wird reines CSS unhandlich: Keyframes muessten dynamisch als Style-Strings generiert werden.
Die Web Animations API (WAAPI) loest dieses Problem, indem sie Animationen als JavaScript-Objekte modelliert statt als statisches CSS. element.animate(keyframes, options) erzeugt eine Animation, die sich programmatisch pausieren, umkehren, in der Geschwindigkeit aendern oder mit anderen Animationen synchronisieren laesst, waehrend sie unter der Haube dieselbe Compositor-Engine nutzt wie CSS-Animationen. Fuer Teams, die bereits viel Erfahrung mit CSS-Animationen haben, ist der Umstieg dabei kein Bruch, sondern eine Erweiterung: dieselben Eigenschaften, dieselbe Easing-Syntax, nur eben als JavaScript-Objekt statt als Stylesheet-Regel formuliert.
2. element.animate(): Grundlagen und Syntax
Die Methode element.animate(keyframes, options) nimmt als erstes Argument ein Array von Keyframe-Objekten oder ein Objekt mit Property-Arrays entgegen, als zweites entweder eine Zahl (Dauer in Millisekunden) oder ein Optionsobjekt mit duration, easing, iterations, delay und weiteren Feldern. Der Rueckgabewert ist ein Animation-Objekt, ueber das sich der Ablauf steuern laesst.
Anders als bei CSS-Keyframes muessen fuer WAAPI-Animationen keine benannten @keyframes-Bloecke im Stylesheet existieren, die Keyframes werden direkt im JavaScript-Aufruf definiert. Das eignet sich besonders fuer Animationen, deren Zwischenwerte erst zur Laufzeit bekannt sind, etwa aus einer Nutzerinteraktion oder einer Serverantwort berechnet.
const box = document.querySelector('.box');
box.animate(
[
{ transform: 'translateX(0px)', opacity: 1 },
{ transform: 'translateX(200px)', opacity: 0.5 },
],
{ duration: 500, easing: 'ease-out', fill: 'forwards' }
);
3. Das Animation-Objekt steuern
Der Rueckgabewert von animate() ist kein Promise, sondern ein Animation-Objekt mit Methoden wie play(), pause(), reverse(), cancel() und finish() sowie einer beschreibbaren playbackRate-Eigenschaft. Damit laesst sich eine laufende Animation jederzeit anhalten, in umgekehrter Richtung fortsetzen oder sofort beenden, etwas, das mit reinem CSS nur ueber Umwege wie Klassenwechsel moeglich ist.
Ein praktisches Beispiel ist eine Animation, die bei erneuter Nutzerinteraktion (etwa einem zweiten Klick waehrend die erste Animation noch laeuft) nahtlos umgekehrt statt neu gestartet werden soll. Mit CSS muesste man dafuer den aktuellen berechneten Style auslesen und eine neue Transition ab diesem Punkt konstruieren, mit WAAPI genuegt animation.reverse().
const animation = box.animate(keyframesArray, { duration: 400, fill: 'both' });
closeButton.addEventListener('click', () => {
if (animation.playState === 'running') {
animation.reverse();
} else {
animation.play();
}
});
4. Zur Laufzeit berechnete Keyframes
Der eigentliche Mehrwert gegenueber CSS zeigt sich, wenn Keyframes von Laufzeitdaten abhaengen, die zum Zeitpunkt des Style-Schreibens nicht bekannt sind. Ein Beispiel: Ein Element soll von seiner aktuellen Bildschirmposition zu den Koordinaten eines anderen, erst zur Laufzeit bestimmten Elements animieren, etwa bei einer 'Add to Cart'-Fly-to-Icon-Animation.
Dafuer werden zunaechst per getBoundingClientRect() Start- und Zielposition ausgelesen, die Differenz berechnet und als transform-Wert in ein dynamisch erzeugtes Keyframe-Array eingesetzt. CSS koennte das nur ueber generierte Inline-Custom-Properties nachbilden, WAAPI nimmt beliebige JavaScript-Werte direkt als Keyframe-Eingabe entgegen, ohne Umweg ueber den Style-String.
function flyToCart(icon, cartElement) {
const start = icon.getBoundingClientRect();
const end = cartElement.getBoundingClientRect();
const dx = end.left - start.left;
const dy = end.top - start.top;
icon.animate(
[
{ transform: 'translate(0, 0) scale(1)', opacity: 1 },
{ transform: `translate(${dx}px, ${dy}px) scale(0.3)`, opacity: 0 },
],
{ duration: 600, easing: 'cubic-bezier(0.4, 0, 1, 1)' }
);
}
5. Timelines und Synchronisation mehrerer Animationen
Jede Animation besitzt eine currentTime-Eigenschaft, ueber die sich der Fortschritt direkt lesen und setzen laesst, unabhaengig von der tatsaechlich vergangenen Echtzeit. Das erlaubt es, mehrere Animationen exakt synchron zu halten, etwa wenn ein komplexes Icon aus mehreren SVG-Pfaden besteht, die alle denselben Bewegungsablauf im Gleichschritt durchlaufen sollen.
Seit einigen Jahren ergaenzt die ScrollTimeline zusaetzlich eine Moeglichkeit, currentTime nicht an die Echtzeit, sondern an den Scroll-Fortschritt eines Containers zu koppeln, wodurch sich scroll-gesteuerte Animationen komplett ohne Scroll-Event-Listener und ohne JavaScript-Rechenlast im Hauptthread umsetzen lassen, ein Bereich, in dem CSS-Transitions grundsaetzlich nicht mithalten koennen.
const animations = [iconPath1, iconPath2, iconPath3].map((el) =>
el.animate(sharedKeyframes, { duration: 800, fill: 'both' })
);
// Alle Animationen exakt beim selben Fortschritt starten
animations.forEach((a) => (a.currentTime = 0));
6. Das finished-Promise und Animationsketten
Jedes Animation-Objekt besitzt eine finished-Eigenschaft, ein Promise, das sich aufloest, sobald die Animation regulaer beendet ist, und ablehnt, wenn sie abgebrochen wird. Das erlaubt es, Animationen mit async/await sauber zu verketten, ohne auf das animationend-Event mit seiner umstaendlicheren Event-Listener-Syntax und den bekannten Problemen bei mehreren gleichzeitig laufenden Animationen auf demselben Element zurueckzugreifen.
Fuer eine Sequenz aus mehreren aufeinanderfolgenden Animationsschritten, etwa Einblenden, kurze Pause, Ausblenden, laesst sich so eine lineare, gut lesbare async-Funktion schreiben, statt verschachtelter setTimeout-Aufrufe oder einer Kette von .then()-Handlern, die bei Aenderungen an der Reihenfolge schnell unuebersichtlich wird.
async function pulseAndFade(el) {
await el.animate(
[{ transform: 'scale(1)' }, { transform: 'scale(1.1)' }, { transform: 'scale(1)' }],
{ duration: 300 }
).finished;
await el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 400, fill: 'forwards' }).finished;
}
7. Performance: Compositor-Thread statt Hauptthread
Ein verbreitetes Missverstaendnis ist, dass JavaScript-gesteuerte Animationen zwangslaeufig langsamer sind als CSS-Animationen, weil sie 'aus JavaScript kommen'. Tatsaechlich nutzt die Web Animations API fuer compositor-faehige Eigenschaften wie transform und opacity dieselbe Rendering-Pipeline wie native CSS-Animationen: Sobald die Keyframes an die Engine uebergeben sind, laeuft die eigentliche Interpolation auf dem Compositor-Thread, unabhaengig vom Hauptthread.
Das bedeutet konkret, dass eine WAAPI-Animation, die nur transform und opacity animiert, auch dann fluessig weiterlaeuft, wenn der Hauptthread durch schwere JavaScript-Berechnungen blockiert ist, genau wie eine aequivalente CSS-Animation. Werden dagegen Layout-ausloesende Eigenschaften wie width, top oder margin animiert, entsteht bei beiden Ansaetzen dieselbe Performance-Belastung durch wiederholtes Layout und Repaint, unabhaengig von der gewaehlten API. In den Chrome DevTools laesst sich das direkt im Performance-Tab nachvollziehen: Eine reine transform/opacity-Animation erzeugt dort ausschliesslich Eintraege im Compositor-Bereich, waehrend eine width- oder top-Animation zusaetzliche Layout- und Paint-Eintraege auf dem Hauptthread produziert, unabhaengig davon, ob sie ueber CSS oder ueber element.animate() gestartet wurde.
8. Wann CSS, wann WAAPI?
Fuer einfache, statische Zustandsuebergaenge wie Hover-Effekte, Fokus-Ringe oder feste Ladeindikatoren bleibt CSS die richtige Wahl: weniger Code, bessere Trennung von Darstellung und Logik, und der Browser kann die Animation bereits beim Style-Parsing optimieren. Sobald Animationen jedoch von Laufzeitwerten abhaengen, unterbrechbar sein muessen oder programmatisch synchronisiert werden sollen, ist WAAPI die praezisere Wahl.
Eine dritte Option verdient Erwaehnung: document.getAnimations() liefert alle aktuell laufenden Animationen der Seite zurueck, unabhaengig davon, ob sie ueber CSS oder WAAPI gestartet wurden. Das erlaubt hybride Ansaetze, etwa CSS fuer die Grundanimation und gezieltes Eingreifen per JavaScript nur in Ausnahmefaellen, ohne die gesamte Animation in JavaScript neu implementieren zu muessen. Fuer Teams, die von reinem CSS auf einen hybriden Ansatz umsteigen, empfiehlt sich ein schrittweises Vorgehen: zunaechst nur die Animationen auf WAAPI umstellen, die tatsaechlich Laufzeitwerte oder Unterbrechbarkeit brauchen, waehrend der Rest unangetastet in CSS bleibt, statt die gesamte Animationsbasis in einem Schritt zu migrieren.
9. Best Practices und Zusammenfassung
Die Web Animations API ist kein Ersatz fuer CSS-Animationen, sondern eine Ergaenzung fuer genau die Faelle, in denen CSS an seine Grenzen stoesst: dynamisch berechnete Keyframes, programmatische Kontrolle ueber laufende Animationen und Synchronisation mehrerer Animationen ueber eine gemeinsame Timeline. Performance-seitig gibt es fuer compositor-faehige Eigenschaften keinen Nachteil gegenueber reinem CSS.
In der Praxis bewaehrt sich eine klare Faustregel: Statische, vorhersehbare Uebergaenge in CSS belassen, dynamische, interaktive oder synchronisierte Animationen in WAAPI umsetzen, und bei beiden konsequent auf transform und opacity statt layout-ausloesende Eigenschaften setzen, um die Compositor-Thread-Performance in beiden Faellen voll auszunutzen.
| Kriterium | CSS-Animation | Web Animations API |
|---|---|---|
| Statische Keyframes | Ideal | Moeglich, aber unnoetig |
| Laufzeitabhaengige Werte | Nur ueber Umwege (Inline-Styles) | Nativ unterstuetzt |
| Programmatische Kontrolle | Eingeschraenkt (Klassenwechsel) | Play/Pause/Reverse/Rate direkt |
| Compositor-Performance (transform/opacity) | Identisch | Identisch |
Mironsoft
Moderne Browser-APIs, Performance und wartbares JavaScript
JavaScript, das im echten Browser robust bleibt, nicht nur im Tutorial?
Wir prüfen bestehenden Frontend-Code auf veraltete Patterns, unnötige Bibliotheken und Performance-Fallen und ersetzen sie durch moderne, native Browser-APIs, die weniger Bundle-Gewicht und weniger Wartungslast bedeuten.
Code-Review
Veraltete Patterns, unnötige Dependencies und Memory Leaks systematisch aufspüren.
Performance-Optimierung
Bundle-Größe, Ladezeit und Runtime-Performance mit modernen APIs verbessern.
Modernisierung
Native Browser-APIs statt schwerer Bibliotheken gezielt einführen.
10. Zusammenfassung
Web Animations API: Das Wichtigste auf einen Blick
Kernmethode
element.animate(keyframes, options) erzeugt eine Animation direkt aus JavaScript-Werten, ohne @keyframes im Stylesheet.
Programmatische Kontrolle
Das zurueckgegebene Animation-Objekt erlaubt play(), pause(), reverse() und eine anpassbare playbackRate.
Performance
Fuer transform und opacity laeuft WAAPI wie CSS-Animationen auf dem Compositor-Thread, unabhaengig vom Hauptthread.
Einsatzregel
Statische Uebergaenge in CSS belassen, dynamische, interaktive oder synchronisierte Animationen ueber WAAPI umsetzen.