CSS-Animationen vs. Web Animations API

Avatar of Ollie Williams
Ollie Williams am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Es gibt eine native API für Animationen in JavaScript, bekannt als die Web Animations API. Wir werden sie in diesem Beitrag WAAPI nennen. MDN hat eine gute Dokumentation dazu, und Dan Wilson hat eine großartige Artikelserie.

In diesem Artikel vergleichen wir WAAPI und CSS-Animationen.

Ein Hinweis zur Browserunterstützung

WAAPI verfügt über ein umfassendes und robustes Polyfill, das es ermöglicht, es heute schon produktiv einzusetzen, auch wenn die Browserunterstützung noch eingeschränkt ist.

Wie immer können Sie Can I Use für Daten zur Browserunterstützung prüfen. Diese liefern jedoch keine sehr guten Informationen zur Unterstützung aller Unterfunktionen von WAAPI. Hier ist ein Prüfer dafür:

Siehe den Pen WAAPI Browser Support Test von Dan Wilson (@danwilson) auf CodePen.

Um alle Funktionen ohne Polyfill auszuprobieren, verwenden Sie Firefox Nightly.

Die Grundlagen von WAAPI

Wenn Sie jemals die Methode .animate() von jQuery verwendet haben, sollte die grundlegende Syntax von WAAPI ziemlich vertraut wirken. 

var element = document.querySelector('.animate-me');
element.animate(keyframes, 1000);

Die Methode animate akzeptiert zwei Parameter: Keyframes und Dauer. Im Gegensatz zu jQuery hat sie nicht nur den Vorteil, dass sie im Browser integriert ist, sondern ist auch performanter.

Das erste Argument, die Keyframes, sollte ein Array von Objekten sein. Jedes Objekt ist ein Keyframe in unserer Animation. Hier ist ein einfaches Beispiel:

var keyframes = [
  { opacity: 0 },
  { opacity: 1 }
];

Das zweite Argument, die Dauer, gibt an, wie lange die Animation dauern soll. Im obigen Beispiel sind es 1000 Millisekunden. Betrachten wir ein aufregenderes Beispiel.

Eine Animista CSS-Animation mit WAAPI nacherstellen

Hier ist ein CSS-Code, den ich von der fantastischen Seite animista für eine Animation namens „slide-in-blurred-top“ übernommen habe. Sieht ziemlich gut aus. 

Die tatsächliche Leistung ist viel besser als dieses GIF.

Hier sind die Keyframes in CSS:

0% {
  transform: translateY(-1000px) scaleY(2.5) scaleX(.2);
  transform-origin: 50% 0;
  filter: blur(40px);
  opacity: 0;
}
100% {
  transform: translateY(0) scaleY(1) scaleX(1);
  transform-origin: 50% 50%;
  filter: blur(0);
  opacity: 1;
}

Hier ist derselbe Code in WAAPI:

var keyframes = [
  { 
    transform: 'translateY(-1000px) scaleY(2.5) scaleX(.2)', 
    transformOrigin: '50% 0', filter: 'blur(40px)', opacity: 0 
  },
  { 
    transform: 'translateY(0) scaleY(1) scaleX(1)',
    transformOrigin: '50% 50%',
    filter: 'blur(0)',
    opacity: 1 
  }
];

Wir haben bereits gesehen, wie einfach es ist, die Keyframes auf jedes Element anzuwenden, das wir animieren möchten.

element.animate(keyframes, 700);

Um das Beispiel einfach zu halten, habe ich nur die Dauer angegeben. Wir können diesen zweiten Parameter jedoch verwenden, um weit mehr Optionen zu übergeben. Zumindest sollten wir auch eine Beschleunigung angeben. Hier ist die vollständige Liste der verfügbaren Optionen mit einigen Beispielwerten:

var options = {
  iterations: Infinity,
  iterationStart: 0,
  delay: 0,
  endDelay: 0,
  direction: 'alternate',
  duration: 700,
  fill: 'forwards',
  easing: 'ease-out',
}
element.animate(keyframes, options);

Mit diesen Optionen beginnt unsere Animation ohne Verzögerung und wird abwechselnd vorwärts und rückwärts wiederholt.

Siehe den Pen motion blur waapi circle von CSS GRID (@cssgrid) auf CodePen.

Ärgerlicherweise variieren einige der Terminologien für uns, die mit CSS-Animationen vertraut sind, von dem, was wir gewohnt sind. Obwohl es auf der positiven Seite ist, dass die Dinge schneller zu tippen sind!

  • Es ist easing statt animation-timing-function.
  • Statt animation-iteration-count ist es iterations. Wenn die Animation für immer wiederholt werden soll, ist es Infinity statt infinite. Etwas verwirrend ist, dass Infinity nicht in Anführungszeichen steht. Infinity ist ein JavaScript-Schlüsselwort, während die anderen Werte Zeichenketten sind.
  • Wir verwenden Millisekunden statt Sekunden, was jedem vertraut sein sollte, der viel JavaScript geschrieben hat. (Sie können auch Millisekunden in CSS-Animationen verwenden, aber nur wenige tun das.)

Werfen wir einen genaueren Blick auf eine der Optionen: iterationStart

Ich war verwirrt, als ich zum ersten Mal auf iterationStart stieß. Warum sollte man bei einer bestimmten Iteration beginnen, anstatt einfach die Anzahl der Iterationen zu verringern? Diese Option ist meist nützlich, wenn Sie eine Dezimalzahl verwenden. Sie könnten sie beispielsweise auf .5 setzen, und die Animation würde auf halbem Weg beginnen. Es braucht zwei Hälften, um ein Ganzes zu ergeben. Wenn also Ihre Iterationsanzahl auf eins gesetzt ist und Ihr iterationStart auf .5 gesetzt ist, wird die Animation von der Hälfte bis zum Ende der Animation abgespielt, dann beginnt sie am Anfang der Animation und endet in der Mitte!

Es ist erwähnenswert, dass Sie auch die Gesamtzahl der Iterationen auf weniger als eins setzen können. Zum Beispiel:

var option = {
  iterations: .5,
  iterationStart: .5
}

Dies würde die Animation von der Mitte bis zum Ende abspielen. 
endDelay: endDelay ist nützlich, wenn Sie mehrere Animationen nacheinander schalten möchten, aber eine Lücke zwischen dem Ende einer Animation und dem Beginn einer nachfolgenden Animation wünschen. Hier ist ein nützliches Video von Patrick Brosset, das dies erklärt.

Beschleunigung (Easing)

Easing ist eines der wichtigsten Elemente in jeder Animation. WAAPI bietet uns zwei verschiedene Möglichkeiten, Easing festzulegen: innerhalb unseres Keyframes-Arrays oder innerhalb unseres Options-Objekts.
In CSS, wenn Sie animation-timing-function: ease-in-out angewendet haben, würden Sie annehmen, dass der Anfang Ihrer Animation sich einblendet und das Ende Ihrer Animation sich ausblendet. Tatsächlich gilt das Easing zwischen den Keyframes, nicht für die gesamte Animation. Dies kann eine feingranulare Kontrolle über das Gefühl einer Animation geben. WAAPI bietet ebenfalls diese Möglichkeit.

var keyframes = [
  { opacity: 0, easing: 'ease-in' }, 
  { opacity: 0.5, easing: 'ease-out' }, 
  { opacity: 1 }
]

Es ist erwähnenswert, dass Sie sowohl in CSS als auch in WAAPI keinen Easing-Wert für den letzten Frame angeben sollten, da dies keine Auswirkung hat. Dies ist ein Fehler, den viele Leute machen.
Manchmal ist es viel intuitiver, das Easing über eine gesamte Animation hinzuzufügen. Dies ist mit CSS nicht möglich, kann aber jetzt mit WAAPI erreicht werden.

var options = {
  duration: 1000,
  easing: 'ease-in-out',
}

Sie können den Unterschied zwischen diesen beiden Arten von Easing in diesem Pen sehen:

Siehe den Pen Same animation, different easing von CSS GRID (@cssgrid) auf CodePen.

Easing vs. Linear

Es ist erwähnenswert, dass es einen weiteren Unterschied zwischen CSS-Animationen und WAAPI gibt: **Der Standard von CSS ist ease, während der Standard von WAAPI linear ist.** Ease ist tatsächlich eine Version von ease-in-out und ist eine ziemlich gute Option, wenn Sie faul sind. Linear hingegen ist todlangweilig und leblos – eine konstante Geschwindigkeit, die mechanisch und unnatürlich aussieht. Es wurde wahrscheinlich als Standard gewählt, da es die neutralste Option ist. Es ist jedoch noch wichtiger, bei der Arbeit mit WAAPI ein Easing anzuwenden als bei der Arbeit mit CSS, damit Ihre Animation nicht langweilig und roboterhaft wirkt.

Performance

WAAPI bietet die gleichen Leistungsverbesserungen wie CSS-Animationen, obwohl dies nicht bedeutet, dass eine reibungslose Animation unvermeidlich ist. 

Ich hatte gehofft, dass die Leistungsoptimierungen dieser API es uns ermöglichen würden, auf die Verwendung von will-change und das völlig hacky translateZ zu verzichten – und irgendwann vielleicht. Zumindest in den aktuellen Browser-Implementierungen können diese Eigenschaften jedoch immer noch hilfreich und notwendig sein, um Jank-Probleme zu bewältigen.

Wenn Sie jedoch eine Verzögerung bei Ihrer Animation haben, müssen Sie sich keine Sorgen um die Verwendung von will-change machen. Der Hauptautor der Web-Animations-Spezifikation hatte einige interessante Ratschläge in der Animation for Work Slack community, die er hoffentlich nicht beanstandet, wenn ich sie hier wiederhole:

Wenn Sie eine positive Verzögerung haben, benötigen Sie kein will-change, da der Browser die Schichtung zu Beginn der Verzögerung vornimmt und wenn die Animation startet, ist sie bereit.

WAAPI im Vergleich zu CSS-Animationen?

WAAPI bietet uns eine Syntax, um in JavaScript das zu tun, was wir bereits in einem Stylesheet tun konnten. Dennoch sollten sie nicht als Rivalen betrachtet werden. Wenn wir uns entscheiden, bei CSS für unsere Animationen und Übergänge zu bleiben, können wir mit WAAPI mit diesen Animationen interagieren.
 

Animations-Objekt

Die Methode .animate() animiert nicht nur unser Element, sie gibt auch etwas zurück. 

var myAnimation = element.animate(keyframes, options);
Animations-Objekt, angezeigt in der Konsole

Wenn wir den Rückgabewert in der Konsole betrachten, sehen wir, dass es sich um ein Animations-Objekt handelt. Dies bietet uns alle möglichen Funktionalitäten, von denen einige selbsterklärend sind, wie myAnimation.pause(). Wir konnten bereits ein ähnliches Ergebnis mit CSS-Animationen erzielen, indem wir die Eigenschaft animation-play-state geändert haben, aber die WAAPI-Syntax ist etwas knapper als element.style.animationPlayState = "paused". Wir haben auch die Möglichkeit, unsere Animation einfach mit myAnimation.reverse() umzukehren, was wiederum nur eine geringfügige Verbesserung gegenüber der Änderung der animation-direction CSS-Eigenschaft mit unserem Skript darstellt.

Allerdings war die Manipulation von @keyframes mit JavaScript bisher nicht gerade einfach. Selbst etwas so Einfaches wie das Neustarten einer Animation erfordert etwas Know-how, wie Chris Coyier bereits geschrieben hat. Mit WAAPI können wir einfach myAnimation.play() verwenden, um die Animation von vorne abzuspielen, wenn sie zuvor abgeschlossen war, oder um sie von der Mitte einer Iteration abzuspielen, wenn wir sie angehalten hatten.

Wir können sogar die Geschwindigkeit einer Animation mit vollständiger Leichtigkeit ändern.

myAnimation.playbackRate = 2; // speed it up
myAnimation.playbackRate = .4; // use a number less than one to slow it down

getAnimations()

Diese Methode gibt ein Array von allen Animations-Objekten für Animationen zurück, die wir mit WAAPI definiert haben, sowie für alle CSS-Übergänge oder Animationen.

element.getAnimations() // returns any animations or transitions applied to our element using CSS or WAAPI

Wenn Sie sich mit CSS für die Definition und Anwendung Ihrer Animationen wohlfühlen, ermöglicht Ihnen getAnimations() die Verwendung der API in Verbindung mit @keyframes. Es ist möglich, CSS weiterhin für den Großteil Ihrer Animationsarbeit zu verwenden und dennoch die Vorteile der API zu nutzen, wenn Sie sie benötigen. Sehen wir uns an, wie einfach das ist.

Selbst wenn ein DOM-Element nur eine Animation angewendet hat, gibt getAnimations() immer ein Array zurück. Greifen wir uns das einzelne Animations-Objekt, um damit zu arbeiten.

var h2 = document.querySelector("h2");
var myCSSAnimation = h2.getAnimations()[0];

Jetzt können wir die Web Animations API auf unsere CSS-Animation anwenden :)

myCSSAnimation.playbackRate = 4;
myCSSAnimation.reverse();

Promises und Events

Wir haben bereits eine Vielzahl von Ereignissen, die von CSS ausgelöst werden und die wir in unserem JavaScript-Code nutzen können: animationstart, animationend, animationiteration und transitionend. Ich muss oft auf das Ende einer Animation oder eines Übergangs warten, um das Element, auf das sie angewendet wurde, aus dem DOM zu entfernen.

Das Äquivalent zur Verwendung von animationend oder transitionend für diesen Zweck in WAAPI würde wieder das Animations-Objekt nutzen:

myAnimation.onfinish = function() {
  element.remove();
}

WAAPI bietet uns die Wahl, sowohl mit Ereignissen als auch mit Promises zu arbeiten. Die Eigenschaft .finished unseres Animations-Objekts gibt ein Promise zurück, das am Ende der Animation aufgelöst wird. So würde das obige Beispiel mit einem Promise aussehen:

myAnimation.finished.then(() =>
  element.remove())

Betrachten wir ein etwas komplexeres Beispiel, das aus dem Mozilla Developer Network übernommen wurde. Promise.all erwartet ein Array von Promises und führt unsere Callback-Funktion erst aus, wenn *alle* diese Promises aufgelöst wurden. Wie wir bereits gesehen haben, gibt element.getAnimations() ein Array von Animations-Objekten zurück. Wir können über alle Animations-Objekte im Array iterieren und bei jedem .finished aufrufen, was uns das benötigte Array von Promises liefert.

In diesem Beispiel wird unsere Funktion erst ausgeführt, nachdem *alle* Animationen auf der Seite abgeschlossen sind.

Promise.all(document.getAnimations().map(animation => 
  animation.finished)).then(function() {           
    // do something cool 
  })

Die Zukunft

Die in diesem Artikel erwähnten Funktionen sind nur der Anfang. Die aktuelle Spezifikation und Implementierung scheinen der Beginn von etwas Großem zu sein.