Mythen entlarven: CSS-Animationen vs. JavaScript

Avatar of Jack Doyle
Jack Doyle am

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

Das Folgende ist ein Gastbeitrag von Jack Doyle, dem Autor der GreenSock Animation Platform (GSAP). Jack arbeitet viel mit Animationen im Browser und hat festgestellt, dass die allgemeine Meinung, "CSS ist schneller", einfach nicht stimmt. Es ist sogar noch mehr als das. Ich lasse ihn erklären.

Es war einmal, da nutzten die meisten Entwickler jQuery, um Dinge im Browser zu animieren. Dieses einblenden, jenes erweitern; einfache Sachen. Als interaktive Projekte aggressiver wurden und mobile Geräte auf den Plan traten, wurde die Leistung immer wichtiger. Flash verschwand und talentierte Animatoren drängten HTML5 dazu, Dinge zu tun, die es noch nie zuvor getan hatte. Sie brauchten bessere Werkzeuge für komplexe Sequenzierungen und erstklassige Leistung. jQuery war einfach nicht dafür konzipiert. Browser reiften und boten Lösungen an.

Die am meisten gefeierte Lösung waren CSS-Animationen (und Transitions). Seit Jahren der Liebling der Branche, wurden CSS-Animationen endlos auf Konferenzen diskutiert, wo Phrasen wie "hardwarebeschleunigt" und "mobilfreundlich" die Ohren des Publikums kitzelten. JavaScript-basierte Animationen wurden behandelt, als wären sie veraltet und "schmutzig". Aber ist das so?

Als jemand, der von Animation und Leistung fasziniert ist (eigentlich schon besessen), bin ich eifrig auf den CSS-Zug aufgesprungen. Ich kam jedoch nicht weit, bevor ich eine Reihe großer Probleme entdeckte, über die niemand sprach. Ich war schockiert.

Dieser Artikel soll das Bewusstsein für einige der gravierenderen Mängel von CSS-basierten Animationen schärfen, damit Sie die Kopfschmerzen, die ich hatte, vermeiden und eine fundiertere Entscheidung treffen können, wann JS und wann CSS für Animationen verwendet werden sollte.

Mangelnde unabhängige Steuerung von Skalierung/Rotation/Position

Das Animieren von Skalierung, Rotation und Position eines Elements ist unglaublich häufig. In CSS sind sie alle in einer "Transform"-Eigenschaft zusammengefasst, was es unmöglich macht, sie auf einem einzelnen Element auf wirklich unterschiedliche Weise zu animieren. Was ist zum Beispiel, wenn Sie "Rotation" und "Skalierung" unabhängig voneinander mit unterschiedlichen Timings und Eases animieren möchten? Vielleicht pulsiert ein Element kontinuierlich (oszillierende Skalierung) und Sie möchten es beim Überfahren drehen. Das ist nur mit JavaScript möglich.

Siehe das Pen Independent Transforms von GreenSock (@GreenSock) auf CodePen

Meiner Meinung nach ist dies eine eklatante Schwäche in CSS, aber wenn Sie nur einfachere Animationen durchführen, die den gesamten Transformationszustand zu einem bestimmten Zeitpunkt animieren, ist dies kein Problem für Sie.

Performance

Die meisten Vergleiche im Web stellen CSS-Animationen gegen jQuery, da es so weit verbreitet ist (als wären "JavaScript" und "jQuery" synonym), aber jQuery ist allgemein dafür bekannt, in Bezug auf die Animationsleistung recht langsam zu sein. Das neuere GSAP ist ebenfalls JavaScript-basiert, aber es ist buchstäblich bis zu 20x schneller als jQuery. Ein Teil des Grundes, warum JavaScript-Animationen einen schlechten Ruf bekamen, ist das, was ich den "jQuery-Faktor" nenne.

Der am häufigsten zitierte Grund für die Verwendung von CSS für Animationen ist „Hardwarebeschleunigung“. Klingt lecker, oder? Lassen Sie es uns in zwei Teile aufteilen

GPU-Beteiligung

Die GPU ist hochoptimiert für Aufgaben wie das Verschieben von Pixeln und das Anwenden von Transformationsmatrizen und Opazität, daher versuchen moderne Browser, diese Aufgaben von der CPU auf die GPU zu verlagern. Das Geheimnis besteht darin, die animierten Elemente auf eigenen GPU-Layern zu isolieren, denn sobald ein Layer erstellt ist (solange sich seine nativen Pixel nicht ändern), ist es für die GPU trivial, diese Pixel zu verschieben und zusammenzusetzen. Anstatt jeden einzelnen Pixel 60 Mal pro Sekunde zu berechnen, kann sie Pixelblöcke (als Layer) speichern und einfach sagen: "Verschiebe diesen Block um 10 Pixel nach rechts und 5 Pixel nach unten" (oder was auch immer).

Randnotiz: Es ist nicht ratsam, jedem Element eine eigene Ebene zu geben, da GPUs nur begrenzten Videospeicher haben. Wenn dieser aufgebraucht ist, wird alles drastisch langsamer.

Wenn Sie Ihre Animationen in CSS deklarieren, kann der Browser bestimmen, welche Elemente GPU-Ebenen erhalten sollen, und sie entsprechend aufteilen. Super.

Aber wussten Sie, dass Sie das auch mit JavaScript machen können? Das Setzen einer Transformation mit einer 3D-Eigenschaft (wie translate3d() oder matrix3d()) veranlasst den Browser, eine GPU-Ebene für dieses Element zu erstellen. Der GPU-Geschwindigkeitsschub ist also nicht nur für CSS-Animationen – JavaScript-Animationen können ebenfalls davon profitieren!

Beachten Sie auch, dass nicht alle CSS-Eigenschaften den GPU-Schub bei CSS-Animationen erhalten. Tatsächlich die meisten nicht. Transformationen (Skalierung, Rotation, Translation und Schrägstellung) und Opazität sind die Hauptnutznießer. Nehmen Sie also nicht einfach an, dass, wenn Sie mit CSS animieren, alles magisch GPU-optimiert wird. Das stimmt einfach nicht.

Auslagerung von Berechnungen an einen anderen Thread

Der andere Teil der „Hardwarebeschleunigung“ hat damit zu tun, dass ein anderer CPU-Thread für animationsbezogene Berechnungen verwendet werden kann. Auch dies klingt in der Theorie großartig, ist aber nicht ohne Kosten, und Entwickler überschätzen oft die Vorteile.

Zunächst können nur Eigenschaften, die den Dokumentfluss nicht beeinflussen, wirklich an einen anderen Thread delegiert werden. Daher sind auch hier Transformationen und Opazität die Hauptnutznießer. Wenn Sie andere Threads abspalten, ist die Verwaltung dieses Prozesses mit Overhead verbunden. Da die Grafikwiedergabe und das Dokumentlayout in den meisten Animationen die meisten Verarbeitungsressourcen verbrauchen (bei WEITEM) (nicht die Berechnung der Zwischenwerte von Eigenschafts-Tweens), ist der Vorteil der Verwendung eines separaten Threads für die Interpolation minimal. Wenn beispielsweise 98 % der Arbeit während einer bestimmten Animation Grafikwiedergabe und Dokumentlayout sind und 2 % die Ermittlung der neuen Positions-/Rotations-/Opazitäts-/was-auch-immer-Werte, selbst wenn Sie diese 10-mal schneller berechnen würden, würden Sie insgesamt nur eine 1 %ige Geschwindigkeitssteigerung feststellen.

Leistungsvergleich

Der folgende Stresstest erstellt eine bestimmte Anzahl von Bildelementen (Punkte) und animiert sie von der Mitte zu zufälligen Positionen an den Rändern mit zufälligen Verzögerungen, wodurch ein Sternenfeld-Effekt entsteht. Erhöhen Sie die Anzahl der Punkte und sehen Sie, wie sich jQuery, GSAP und Zepto vergleichen. Da Zepto CSS-Übergänge für alle seine Animationen verwendet, sollte es die beste Leistung erbringen, richtig?

Siehe das Pen Speed Test: GSAP vs Zepto (CSS Transitions) vs jQuery von GreenSock (@GreenSock) auf CodePen

Die Ergebnisse bestätigen, was im Web weit verbreitet ist – CSS-Animationen sind deutlich schneller als jQuery. Auf den meisten von mir getesteten Geräten und Browsern schnitt jedoch das JavaScript-basierte GSAP sogar besser ab als CSS-Animationen (in einigen Fällen deutlich besser, z. B. auf dem Microsoft Surface RT war GSAP wahrscheinlich mindestens 5-mal schneller als die von Zepto erstellten CSS-Übergänge, und auf dem iPad 3 iOS7 waren Transformationen deutlich schneller, wenn sie mit GSAP anstelle von CSS-Übergängen animiert wurden)

Animierte Eigenschaften Besser mit JavaScript Besser mit CSS
Oben, links, Breite, Höhe Windows Surface RT, iPhone 5s (iOS7), iPad 3 (iOS 6), iPad 3 (iOS7), Samsung Galaxy Tab 2, Chrome, Firefox, Safari, Opera, Kindle Fire HD, IE11 (keine)
Transformationen (Translate/Scale) Windows Surface RT, iPhone 5s (iOS7), iPad 3 (iOS7), Samsung Galaxy Tab 2, Firefox, Opera, IE11 iPad 3 (iOS6), Safari, Chrome
Genau wie viel „besser“? Die Originalversion des Tests hatte einen Frames-per-Second-Zähler für quantifizierbare Ergebnisse, aber es wurde schnell klar, dass es keine wirklich genaue Methode gibt, die FPS über Browser hinweg zu messen, insbesondere bei CSS-Animationen, und bestimmte Browser meldeten irreführende Zahlen, daher habe ich ihn entfernt. Sie können die relative Leistung jedoch leicht einschätzen, indem Sie die Anzahl der Punkte erhöhen, zwischen den Engines wechseln und beobachten, wie sich die Dinge verhalten (flüssige Bewegung, gleichmäßiges Timing und Punktverteilung usw.). Schließlich ist das Ziel, dass Animationen gut aussehen.

Interessante Beobachtungen

  • Beim Animieren von top/left/width/height (Eigenschaften, die den Dokumentfluss beeinflussen) war JavaScript durchweg schneller (GSAP, nicht jQuery).
  • Einige Geräte schienen stark für Transformationen optimiert zu sein, während andere Top/Left/Width/Height-Animationen besser verarbeiteten. Besonders bemerkenswert ist, dass das ältere iOS6 mit CSS-animierten Transformationen viel besser zurechtkam, aber das neuere iOS7 drehte sich um, und jetzt sind sie deutlich langsamer (ich habe darüber hier gebloggt).
  • Es gibt eine erhebliche Verzögerung beim initialen Start von CSS-Animationen, da der Browser Ebenen berechnet und die Daten auf die GPU hochlädt. Dies gilt auch für JavaScript-basierte 3D-Transformationen, sodass "GPU-Beschleunigung" nicht ohne eigene Kosten einhergeht.
  • Unter starker Belastung waren CSS-Übergänge anfälliger dafür, in Bändern/Ringen aufzusprühen (dies scheint ein Synchronisierungs-/Planungsproblem zu sein, möglicherweise aufgrund ihrer Verwaltung in einem anderen Thread).
  • In einigen Browsern (wie Chrome) tötete eine sehr hohe Anzahl animierter Punkte das Deckkraft-Ausblenden des Textes vollständig, aber nur bei Verwendung von CSS-Animationen!

Obwohl gut optimiertes JavaScript oft genauso schnell, wenn nicht sogar schneller als CSS-Animationen ist, sind 3D-Transformationen tendenziell schneller, wenn sie mit CSS animiert werden, was jedoch viel mit der Art und Weise zu tun hat, wie Browser heute 16-Element-Matrizen handhaben (Erzwingen der Konvertierung von Zahlen in eine verkettete Zeichenkette und zurück in Zahlen). Hoffentlich wird sich das jedoch ändern. In den meisten realen Projekten würde man den Leistungsunterschied sowieso nicht bemerken.

Ich möchte Sie ermutigen, Ihre eigenen Tests durchzuführen, um zu sehen, welche Technologie in Ihrem speziellen Projekt(en) die flüssigste Animation liefert. Glauben Sie nicht dem Mythos, dass CSS-Animationen immer schneller sind, und gehen Sie auch nicht davon aus, dass der obige Geschwindigkeitstest widerspiegelt, was Sie in Ihren Apps sehen würden. Testen, testen, testen.

Laufzeitsteuerung und Ereignisse

Einige Browser erlauben Ihnen, eine CSS-Keyframe-Animation anzuhalten/fortzusetzen, aber das war’s auch schon. Sie können nicht zu einer bestimmten Stelle in der Animation springen, Sie können auch nicht reibungslos teilweise umkehren oder die Zeitskala ändern oder Callbacks an bestimmten Stellen hinzufügen oder diese an eine reichhaltige Reihe von Wiedergabeereignissen binden. JavaScript bietet eine große Kontrolle, wie in der folgenden Demo gezeigt.

Siehe das Pen Impossible with CSS: controls von GreenSock (@GreenSock) auf CodePen

Moderne Animation ist sehr eng mit Interaktivität verbunden, daher ist es unglaublich nützlich, von variablen Startwerten zu variablen Endwerten animieren zu können (z. B. basierend darauf, wohin der Benutzer klickt), oder Dinge spontan zu ändern, aber deklarative CSS-basierte Animationen können das nicht.

Workflow

Für einfache Übergänge zwischen zwei Zuständen (z. B. Rollovers oder expandierende Menüs usw.) sind CSS-Transitions großartig. Zum Sequenzieren von Dingen müssen Sie jedoch in der Regel CSS-Keyframe-Animationen verwenden, die Sie zwingen, Dinge in Prozentangaben zu definieren, wie z. B.

@keyframes myAnimation {
  0% {
    opacity: 0;
    transform: translate(0, 0);
  }
  30% {
    opacity: 1;
    transform: translate(0, 0);
  }
  60% {
    transform: translate(100px, 0);
  }
  100% {
    transform: translate(100px, 100px);
  }
}
#box {
   animation: myAnimation 2.75s;
}

Aber denken Sie beim Animieren nicht eher in Zeit als in Prozenten? Wie "die Deckkraft für 1 Sekunde einblenden, dann für 0,75 Sekunden nach rechts schieben und 1 Sekunde später bis zum Stillstand abprallen". Was passiert, wenn Sie stundenlang eine komplizierte Sequenz in Prozentangaben erstellen und der Kunde dann sagt: "Machen Sie den Teil in der Mitte 3 Sekunden länger"? Autsch. Sie müssten ALLE Prozentangaben neu berechnen!

Normalerweise beinhaltet die Erstellung von Animationen viel Experimentieren, insbesondere mit Timing und Eases. Hier wäre eine seek()-Methode tatsächlich sehr nützlich. Stellen Sie sich vor, Sie erstellen eine 60-sekündige Animation Stück für Stück und feilen dann an den letzten 5 Sekunden; Sie müssten die ersten 55 Sekunden jedes Mal durchlaufen, wenn Sie die Ergebnisse Ihrer Bearbeitungen an den letzten Teilen sehen möchten. Igitt. Mit einer seek()-Methode könnten Sie das während der Produktion einfach einfügen, um zu dem Teil zu springen, an dem Sie arbeiten, und es dann entfernen, wenn Sie fertig sind. Ein großer Zeitersparnis.

Es wird immer üblicher, Canvas-basierte Objekte und andere Bibliotheks-Objekte von Drittanbietern zu animieren, aber leider können CSS-Animationen nur DOM-Elemente ansprechen. Das bedeutet, wenn Sie viel Zeit und Energie in CSS-Animationen investieren, wird sich das nicht auf diese anderen Projekttypen übertragen. Sie müssen Ihre Animations-Toolsets wechseln.

Es gibt noch ein paar weitere Komfortfunktionen im Workflow, die bei CSS-Animationen fehlen

  • Relative Werte. Zum Beispiel „die Rotation um 30 Grad mehr animieren“ oder „das Element um 100px von seiner Startposition nach unten bewegen“.
  • Verschachtelung. Stellen Sie sich vor, Sie könnten Animationen erstellen, die in eine andere Animation verschachtelt werden können, die selbst wieder verschachtelt werden kann usw. Stellen Sie sich vor, Sie steuern diese Master-Animation, während alles perfekt synchron bleibt. Diese Struktur würde modularen Code fördern, der viel einfacher zu produzieren und zu warten ist.
  • Fortschrittsbericht. Ist eine bestimmte Animation abgeschlossen? Wenn nicht, wo genau steht sie im Hinblick auf ihren Fortschritt?
  • Gezieltes Beenden. Manchmal ist es unglaublich nützlich, alle Animationen zu beenden, die die "Skalierung" eines Elements (oder welche Eigenschaften Sie auch immer möchten) beeinflussen, während der Rest weiterlaufen darf.
  • Prägnanter Code. CSS-Keyframe-Animationen sind ausführlich, selbst wenn man alle redundanten Herstellerpräfixe nicht berücksichtigt. Jeder, der versucht hat, etwas auch nur mäßig Komplexes zu erstellen, wird bestätigen, dass CSS-Animationen schnell umständlich und unhandlich werden. Tatsächlich kann das schiere Volumen an notwendigem CSS zur Bewältigung von Animationsaufgaben das Gewicht einer JavaScript-Bibliothek übersteigen (die einfacher zu cachen und über viele Animationen hinweg wiederzuverwenden ist).

Begrenzte Effekte

Mit CSS-Animationen können Sie Folgendes nicht wirklich tun:

  • Entlang einer Kurve animieren (wie ein Bézierpfad).
  • Interessante Eases wie Elastic, Bounce oder eine grobe Ease verwenden. Es gibt eine cubic-bezier()-Option, aber sie erlaubt nur 2 Kontrollpunkte, daher ist sie ziemlich begrenzt.
  • Verwenden Sie in einer CSS-Keyframe-Animation verschiedene Eases für verschiedene Eigenschaften; Eases gelten für den gesamten Keyframe.
  • Physikbasierte Bewegung. Zum Beispiel das sanfte, auf Impuls basierende Flickern und Zurückfedern, das in dieser Draggable-Demo implementiert ist.
  • Die Scrollposition animieren
  • Gerichtete Rotation (z. B. „Animation auf genau 270 Grad in der kürzesten Richtung, im oder gegen den Uhrzeigersinn“).
  • Attribute animieren.

Kompatibilität

CSS-basierte Animation funktioniert nicht in IE9 und früher. Die meisten von uns hassen die Unterstützung älterer Browser (insbesondere IE), aber die Realität ist, dass einige unserer Kunden diese Unterstützung benötigen.

Browser-Präfixe sind für viele Browser notwendig, aber Sie können Preprocessing-Tools nutzen, um das manuelle Schreiben zu vermeiden.

Fazit

Sind CSS-Animationen "schlecht"? Sicherlich nicht. Tatsächlich sind sie hervorragend für einfache Übergänge zwischen Zuständen (wie Rollovers), wenn die Kompatibilität mit älteren Browsern nicht erforderlich ist. 3D-Transformationen funktionieren normalerweise sehr gut (iOS7 ist eine bemerkenswerte Ausnahme), und CSS-Animationen können für Entwickler sehr attraktiv sein, die es vorziehen, ihre gesamte Animations- und Präsentationslogik in der CSS-Schicht abzulegen. JavaScript-basierte Animationen bieten jedoch weit mehr Flexibilität, einen besseren Workflow für komplexe Animationen und reichhaltige Interaktivität, und sie sind oft genauso schnell (oder sogar schneller) als CSS-basierte Animationen, entgegen dem, was Sie vielleicht gehört haben.

Verglichen mit jQuery.animate() kann ich verstehen, warum CSS-Animationen so attraktiv waren. Wer bei klarem Verstand würde nicht die Chance nutzen, einen 10-fachen Leistungszuwachs zu erzielen? Aber es ist nicht mehr die Wahl zwischen jQuery und CSS-Animationen; JavaScript-basierte Tools wie GSAP eröffnen völlig neue Möglichkeiten und beseitigen die Leistungslücke.

In diesem Artikel geht es nicht um GSAP oder eine bestimmte Bibliothek; der Punkt ist, dass JavaScript-basierte Animationen keinen schlechten Ruf verdienen. Tatsächlich ist JavaScript die einzige Wahl für ein wirklich robustes, flexibles Animationssystem. Außerdem wollte ich einige der frustrierenden Aspekte von CSS-Animationen beleuchten (über die niemand zu sprechen scheint), damit Sie letztendlich eine fundiertere Entscheidung darüber treffen können, wie Sie im Browser animieren.

Wird die Web Animations Spezifikation die Dinge lösen?

Das W3C arbeitet an einer neuen Spezifikation namens Web Animations, die viele der Mängel in CSS-Animationen und CSS-Transitions beheben soll, indem sie bessere Laufzeitsteuerungen und zusätzliche Funktionen bietet. Es scheint sicherlich in vielerlei Hinsicht ein Fortschritt zu sein, aber es hat immer noch Mängel (einige davon sind wahrscheinlich aufgrund der Notwendigkeit der Legacy-Unterstützung bestehender CSS-Spezifikationen unüberwindbar, so dass zum Beispiel eine unabhängige Steuerung von Transformationskomponenten unwahrscheinlich ist). Das ist jedoch ein ganz anderer Artikel. Wir werden abwarten müssen, wie sich die Dinge entwickeln. Es arbeiten definitiv einige kluge Köpfe an der Spezifikation.