Herzlich willkommen zum SELF-Treffen 2026
vom 24.04. – 26.04.2026
in Halle (Saale)
Web Animations/Animationen steuern
Einer der Nachteile von CSS-Animation und Transition ist das Fehlen von Möglichkeiten zur Steuerung.
Dieses Tutorial zeigt, wie man mit der Web Animations API Animationen durch den Benutzer kontrollieren lassen kann. Diese API findet sich nativ in allen Browsern, externe Bibliotheken wie GSAP oder Anime.js werden nicht benötigt.
Dabei animieren wir nicht nur Gimmicks und Show-Effekte, sondern aussagekräftige Inhalte und die Aufmerksamkeit des Benutzers.
Inhaltsverzeichnis
CSS-Animation
Das letzte Beispiel aus dem CSS-Animation-Tutorial zeigt einen Loading-Spinner, der bei :hover pausiert:
#gear {
animation: run 3500ms linear infinite;
}
#gear:hover {
animation-play-state: paused;
}
@keyframes run {
0% {
rotate: 0;
}
100% {
rotate: 1turn;
}
}
Die Eigenschaft animation-play-state wird beim Überfahren mit der Maus auf animation-play-state: paused; gesetzt. Die Animation pausiert, bis sich die Maus wieder entfernt.
Weitere Steuerungsmöglichkeiten wie eine Änderung der Richtung oder Geschwindigkeit sind nicht möglich.
Und das Gleiche mit JavaScript
Gerade, wenn Nutzer Einfluss nehmen, sind Tricks wie das Selektieren mit target ein Verstoß gegen die Trennung von Inhalt, Präsentation und Verhalten. Deshalb nehmen wir jetzt JavaScript zu Hilfe und bauen unseren Loading-Spinner nach:
const animation = gear.animate(
[
{ rotate: "0" },
{ rotate: "1turn" }
],
{
duration: 3500,
iterations: Infinity
}
);
playBtn.addEventListener("click", () => {
animation.play();
});
pauseBtn.addEventListener("click", () => {
animation.pause();
});
In diesem Beispiel wird in einem script-Bereich eine Konstante animation definiert. Sie ruft für gear die Element.animate(keyframes, options)-Methode auf, die ein Animations-Objekt erzeugt.
Dies enthält zwei Parameter:
-
keyframesentsprechen den @keyframes von CSS- Es enthält einen Array der CSS-Eigenschaft(en) – hier nur rotate und die zu verändernden Werte.
(Alternativ wäre auch eine Objektschreibweise möglich, in der CSS-Eigenschaften einen Array der zu animierenden Werte enthalten.)
- Es enthält einen Array der CSS-Eigenschaft(en) – hier nur rotate und die zu verändernden Werte.
- In den
optionsfinden sich die- duration, die Dauer in ms
- iterations, die Anzahl der Wiederholungen.
Wie in CSS-Animation startet eine Web Animation sofort beim Laden. Falls man das nicht will, muss ein animation.pause(); eingefügt werden.
- In JS sind nur Zeitangaben im
msmöglich, in CSS gegen sowohlmsals auchs. - In JS lautet das Schlüsswort für unendliche Wiederholungen
Infinity– in CSSinfinite - In JS verlaufen Animation standardmäßig
linear, in CSS ist der Default für animation-timing-functionease-in. - CSS-Eigenschaften mit Bindestrich werden in JavaScript in CamelCase notiert.
Pausieren bei :hover?
Es gibt kein einfaches Abfragen des :hover-Selektors in JavaScript. Stattdessen können die Maus-Events überprüft werden:
gear.addEventListener("mouseenter", () => animation.pause());
gear.addEventListener("mouseleave", () => animation.play());
Mit AddEventListener werden die mouseenter und mouseleave-Events belauscht. Feuern sie, wird die Animation pausiert oder wieder aufgenommen.
Cards mit WAAPI
Ein klassisches Problem bei CSS-Animationen ist ihre Einbahnstraße.
Sehr oft wünscht man sich: Die gleiche Animation soll vorwärts und rückwärts laufen können.
Mit reinem CSS führt das schnell zu doppeltem Code, mehreren @keyframes oder komplizierten Workarounds.
Ein typisches Beispiel ist eine Karte, die von einem Stapel ausgeteilt wird:
- Erster Klick → die Karte wird ausgeteilt und „fliegt “ vom Stapel
- Zweiter Klick → sie werden wieder auf den Stapel zurückgelegt.
Idealerweise mit derselben Animation – nur rückwärts
Genau an diesem Punkt zeigt die Web Animations API ihre Stärke:
Animationen sind keine starren Deklarationen mehr, sondern steuerbare Objekte, deren Richtung jederzeit geändert werden kann.
Als Grundlage nehmen wir die Flip-Cards aus dem Transform-Tutorial.
<button class="karte" data-seite="links" data-karte="1">
<span class="rueckseite"></span>
<span class="vorderseite"></span>
</button>
Ein Button als interaktives Element erhält zwei span-Elemente für die Vorderseite und die Rückseite. Die Kartenfront wird mit einem Hintergrundbild aus Wikimedia Commons realisiert, für den Kartenrücken wird ein sich weiderholender Verlauf verwendet.
Animationen mit JavaScript steuern
Diese Spielkarten sollen nun angeklickt und damit ausgelegt werden. Ein erneuter Klick legt sie zurück:
const KARTENBREITE_VW = 15;
const MAX_FLUGHOEHE_FAKTOR = 2; // Flughöhe als Vielfaches der Kartenbreite
const MAX_FLUGHOEHE = (KARTENBREITE_VW * MAX_FLUGHOEHE_FAKTOR * window.innerWidth) / 100;
const ANIMATION_DAUER = 1500; // Dauer der Animation in Millisekunden
const animation = karte.animate([
{
transform: `translateX(${startX}px) translateZ(${startZ}px) rotateY(${startRotateY}deg) rotateX(0deg)`,
offset: 0
},
{
transform: `translateX(${startX + (endX - startX) * 0.3}px) translateZ(${Math.max(startZ, endZ) + MAX_FLUGHOEHE * 0.8}px) rotateY(${startRotateY + (endRotateY - startRotateY) * 0.3}deg) rotateX(30deg)`,
offset: 0.3
},
{
transform: `translateX(${startX + (endX - startX) * 0.5}px) translateZ(${Math.max(startZ, endZ) + MAX_FLUGHOEHE}px) rotateY(${(startRotateY + endRotateY) / 2}deg) rotateX(40deg)`,
offset: 0.5
},
{
transform: `translateX(${startX + (endX - startX) * 0.7}px) translateZ(${Math.max(startZ, endZ) + MAX_FLUGHOEHE * 0.8}px) rotateY(${startRotateY + (endRotateY - startRotateY) * 0.7}deg) rotateX(20deg)`,
offset: 0.7
},
{
transform: `translateX(${endX}px) translateZ(${endZ}px) rotateY(${endRotateY}deg) rotateX(0deg)`,
offset: 1
}
], {
duration: ANIMATION_DAUER,
easing: 'ease-in-out',
fill: 'forwards'
});
Das Bedienfeld besteht aus drei button-Elementen mit ids. Den Buttons werden mit addEventListener Eventhandler hinzugefügt.
Jede Animation kann ausgelöst, pausiert und wieder umgekehrt werden.
Unterbrechung und Zustandsrobustheit
Bisher funktioniert die Karte hervorragend, wenn sich der Benutzer korrekt verhält:
Klicken > Warten > Erneut klicken
Echte Benutzer tun das jedoch nicht! Sobald jemand zweimal schnell hintereinander oder während der Animation klickt, beginnen CSS-Übergänge instabil zu werden. Genau hier glänzt WAAPI.
function aktualisiereTimelineInfo() {
if (!animationsTracker) {
timelineInfo.textContent = 'Keine Animation aktiv';
timelineInfo.classList.remove('active');
return;
}
const vergangeneZeit = Date.now() - animationsTracker.startZeit;
const fortschritt = Math.min(100, (vergangeneZeit / ANIMATION_DAUER) * 100);
const vergangeneMs = Math.min(ANIMATION_DAUER, vergangeneZeit);
timelineInfo.textContent = `Karte ${animationsTracker.karteNr}: ${Math.round(vergangeneMs)}ms / ${ANIMATION_DAUER}ms (${fortschritt.toFixed(1)}%)`;
timelineInfo.classList.add('active');
if (vergangeneZeit < ANIMATION_DAUER && animationsTracker.animation.playState === 'running') {
requestAnimationFrame(aktualisiereTimelineInfo);
}
}
Wenn während einer Animation erneut geklickt wird, wird die aktuelle Timeline angezeigt, die Animation selbst aber weiter ausgeführt.
Kräuterlexikon
Ein typisches Beispiel ist eine Karte, die zu einer Detailansicht erweitert wird:
- Erster Klick → die Karte klappt auf
- Zweiter Klick → sie schließt sich wieder
Idealerweise mit derselben Animation – nur rückwärts
Für ein Kräuterlexikon wollen wir Cards anlegen, bei denen der Nutzer auf Wunsch weitere Informationen erhalten kann:
STATE 0 [ Bild ] [ teaser ] [ Mehr erfahren ] STATE 1 [ image (dimmed) ] [ Pflegehinweise ] STATE 2 [ image (dimmed) ] [ Heilwirkung ]
HTML-Markup
Jede Karte besteht aus einer Überschrift und einem Bild, über dem sowohl der Teaser als auch ein [Mehr erfahren]-Button gelegt werden.
Bei einem Klick auf den Button werden weitere Details eingeblendet.
<div class="card">
<header>
<h2>Basilikum<span class="icon">🌱</span></h2>
</header>
<img src="Ocimum_basilicum.jpg" alt="basil">
<p class="card-teaser"> Ein aromatisches Küchenkraut, perfekt für Pasta, Salate und Pesto.</p>
<button class="toggle" aria-expanded="false">Mehr erfahren</button>
<section class="card-panel" data-step="1" hidden>
<h3>Pflegehinweise</h3>
<dl class="care">
<dt>Standort</dt>
<dd>Hell, aber keine direkte Mittagssonne</dd>
...
</dl>
</section>
<section class="card-panel" data-step="2" hidden>
...
</section>
</div>
Wir haben eine Karte mit header, teaser und einem button, der section-Elemente mit Pflegehinweisen der Wirkung aufklappen kann. Sie sind anfänglich mit dem hidden-Attribut ausgeblendet.
Die Karte ist immer dieselbe – nur ihr Informationsgrad verändert sich.
Die Buttons sind eigentlich „unnatürlich“ für echte UIs, hier machen sie die Zeit sichtbar!
details.hidden = false;
const detailsAnimation = details.animate(
[
{ opacity: 0, translate: "0 1em" },
{ opacity: 1, translate: "0 0" }
],
{
duration: 300,
easing: "ease",
fill: "both",
delay: 150
}
);
detailsAnimation.pause();
Das hidden-Attribut wird nicht animiert, sondern nur auf false gesetzt. Stattdessen wird ein Animationsobjekt angelegt, dass im keyframe-Objekt die opacity auf 0 setzt und den Textinhalt mit translate um 1em verschiebt.
Sobald das Animations-Objekt mit detailsAnimation.play(); aufgerufen wird, werden die Werte für opacity und translate animiert und der Textinhalt erscheint.
Wir haben kein Klassen-Geschiebe von button und .card-details, keine doppelten keyframes, sondern bewegen uns auf demselben Zeitstrahl vor und zurück.
Die sorgt für eine bessere separation of concerns:
- Layout → CSS (width, aspect-ratio)
- Bewegung → WAAPI
- Zustände → JS
Sowohl die Keyframes als auch die Timing Options werden in Literal-Schreibweise notiert. Die einzelnen Werte werden durch Kommata getrennt; am Ende einer Aufzählung darf aber kein Komma mehr stehen.
Dieser Umstand sorgt immer wieder für Fehlerquellen und sollte deshalb von jedem Script-Autor jedes Mal peinlich genau überprüft werden.
Wenn man einfach nur Inhalte aufklappen will, eignet sich ein details-Element besser. In unserem Beispiel wollen wir aber denselben Übergang vorwärts und rückwärts abspielen, pausieren und jederzeit fortsetzen können.
Fazit
Mit diesem Script haben wir einen Player, der eine Animation abspielen, auf Wunsch aber auch stoppen oder rückwärts laufen lassen kann. Im nächsten Kapitel wollen wir mehrere Karten und deren Animationen kombinieren und in einer timeline nacheinander synchronisieren.
- Rube-Goldberg-Maschinen

einfache Maschinen mit animierten Transforms