Alles, was Sie über FLIP-Animationen in React wissen müssen

Avatar of Kirill Vasiltsov
Kirill Vasiltsov am

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

Mit einem sehr aktuellen Safari-Update wird die Web Animations API (WAAPI) in allen modernen Browsern (außer IE) jetzt ohne Flag unterstützt. Hier ist ein praktischer Pen, mit dem Sie überprüfen können, welche Funktionen Ihr Browser unterstützt. Die WAAPI ist eine gute Möglichkeit, Animationen (die in JavaScript durchgeführt werden müssen) zu erstellen, da sie nativ ist – das bedeutet, sie erfordert keine zusätzlichen Bibliotheken. Wenn Sie neu bei WAAPI sind, hier ist eine sehr gute Einführung von Dan Wilson.

Einer der effizientesten Ansätze für Animationen ist FLIP. FLIP benötigt ein wenig JavaScript, um zu funktionieren. 

Werfen wir einen Blick auf die Schnittmenge von WAAPI, FLIP und der Integration all dessen in React. Aber wir beginnen zuerst ohne React und kommen dann dazu.

FLIP und WAAPI

FLIP-Animationen werden durch die WAAPI erheblich erleichtert!

Kurze Auffrischung zu FLIP: Die Kernidee ist, dass Sie das Element dort positionieren, wo es am Ende sein soll. Als Nächstes wenden Sie Transformationen an, um es in die Ausgangsposition zu bewegen. Dann machen Sie diese Transformationen rückgängig. 

Das Animieren von Transformationen ist extrem effizient, daher ist FLIP extrem effizient. Vor WAAPI mussten wir die Stile eines Elements direkt manipulieren, um Transformationen festzulegen und auf den nächsten Frame warten, um sie aufzuheben/umzukehren.

// FLIP Before the WAAPI
el.style.transform = `translateY(200px)`;


requestAnimationFrame(() => {
  el.style.transform = '';
});

Viele Bibliotheken basieren auf diesem Ansatz. Es gibt jedoch mehrere Probleme damit

  • Alles fühlt sich wie ein riesiger Hack an.
  • Es ist extrem schwierig, die FLIP-Animation umzukehren. Während CSS-Transformationen „kostenlos“ umgekehrt werden, sobald eine Klasse entfernt wird, ist dies hier nicht der Fall. Das Starten eines neuen FLIPs, während ein vorheriges läuft, kann zu Fehlern führen. Das Umkehren erfordert das Parsen einer Transformationsmatrix mit getComputedStyles und deren Verwendung zur Berechnung der aktuellen Abmessungen, bevor eine neue Animation festgelegt wird.
  • Fortgeschrittene Animationen sind fast unmöglich. Um beispielsweise zu verhindern, dass die Kindelemente eines skalierten Elternteils verzerrt werden, müssen wir in jedem Frame Zugriff auf den aktuellen Skalierungswert haben. Dies kann nur durch Parsen der Transformationsmatrix erfolgen.
  • Es gibt viele Browser-Eigenheiten. Zum Beispiel erfordert es manchmal, manchmal, um eine FLIP-Animation in Firefox fehlerfrei zum Laufen zu bringen, dass requestAnimationFrame zweimal aufgerufen wird.
requestAnimationFrame(() => {
  requestAnimationFrame(() => {
    el.style.transform = '';
  });
});

All diese Probleme haben wir nicht, wenn WAAPI verwendet wird. Das Umkehren kann mühelos mit der reverse-Funktion erfolgen. Das Gegen-Skalieren von Kindern ist ebenfalls möglich. Und wenn es einen Fehler gibt, ist es einfach, den genauen Schuldigen zu finden, da wir nur mit einfachen Funktionen wie animate und reverse arbeiten und nicht Dinge wie den requestAnimationFrame-Ansatz durchsuchen müssen. 

Hier ist die Gliederung der WAAPI-Version

el.classList.toggle('someclass');
const keyframes = /* Calculate the size/position diff */;
el.animate(keyframes, 2000);

FLIP und React

Um zu verstehen, wie FLIP-Animationen in React funktionieren, ist es wichtig zu wissen, wie und vor allem warum sie in reinem JavaScript funktionieren. Erinnern Sie sich an die Anatomie einer FLIP-Animation

Diagram. Cache current site and position, make a style change, get new size and position, calculate the difference, set transforms, and cancel transforms. Each item has a purple background, except the last one, indicating they happen before paint.

Alles mit violettem Hintergrund muss vor dem „Paint“-Schritt des Renderns geschehen. Andernfalls würden wir für einen Moment einen Blitz neuer Stile sehen, was nicht gut ist. In React wird es etwas komplizierter, da alle DOM-Updates für uns durchgeführt werden.

Die Magie von FLIP-Animationen besteht darin, dass ein Element transformiert wird, bevor der Browser die Möglichkeit hat, es zu rendern. Woher wissen wir also den „Vor-dem-Rendern“-Moment in React?

Lernen Sie den useLayoutEffect-Hook kennen. Wenn Sie sich jemals gefragt haben, wofür er da ist... dafür ist er da! Alles, was wir in diesen Callback übergeben, geschieht synchron nach DOM-Updates, aber vor dem Rendern. Mit anderen Worten, dies ist ein großartiger Ort, um ein FLIP einzurichten!

Machen wir etwas, wofür die FLIP-Technik sehr gut geeignet ist: Animieren der DOM-Position. CSS kann nichts tun, wenn wir animieren wollen, wie sich ein Element von einer DOM-Position zu einer anderen bewegt. (Stellen Sie sich vor, Sie erledigen eine Aufgabe in einer To-Do-Liste und verschieben sie in die Liste der „erledigten“ Aufgaben, so wie wenn Sie auf Elemente im folgenden Pen klicken.)

Betrachten wir das einfachste Beispiel. Wenn Sie auf eines der beiden Quadrate im folgenden Pen klicken, tauschen sie die Positionen. Ohne FLIP würde dies sofort geschehen.

Dort passiert viel. Beachten Sie, wie die gesamte Arbeit innerhalb von Lebenszyklus-Hook-Callbacks erfolgt: useEffect und useLayoutEffect. Was es etwas verwirrend macht, ist, dass die Zeitleiste unserer FLIP-Animation aus dem Code allein nicht ersichtlich ist, da sie über zwei React-Renderings hinweg stattfindet. Hier ist die Anatomie einer React-FLIP-Animation, um die unterschiedliche Reihenfolge der Operationen zu zeigen

Diagram. Cache the size and position, retrieve previous size and position from cache, get new size and position, calculate the difference, and play the animation.

Obwohl useEffect immer nach useLayoutEffect und nach dem Browser-Rendering ausgeführt wird, ist es wichtig, dass wir die Position und Größe des Elements nach dem ersten Rendern cachen. Wir werden keine Chance haben, dies beim zweiten Rendern zu tun, da useLayoutEffect nach allen DOM-Updates ausgeführt wird. Aber das Verfahren ist im Wesentlichen dasselbe wie bei Vanilla-FLIP-Animationen.

Einschränkungen

Wie die meisten Dinge gibt es auch bei der Arbeit mit FLIP in React einige Vorbehalte zu beachten.

Unter 100 ms halten

Eine FLIP-Animation ist eine Berechnung. Berechnungen brauchen Zeit, und bevor Sie diese flüssige 60-fps-Transformation anzeigen können, müssen Sie ziemlich viel Arbeit leisten. Die Leute werden keine Verzögerung bemerken, wenn sie unter 100 ms liegt, also stellen Sie sicher, dass alles darunter liegt. Der Performance-Tab in DevTools ist ein guter Ort, um dies zu überprüfen.

Unnötige Renderings

Wir können useState nicht zum Cachen von Größe, Positionen und Animations-Objekten verwenden, da jedes setState ein unnötiges Rendering verursacht und die App verlangsamt. Im schlimmsten Fall kann es sogar zu Fehlern führen. Versuchen Sie stattdessen, useRef zu verwenden und betrachten Sie es als ein Objekt, das ohne Rendering verändert werden kann.

Layout-Thrashing

Vermeiden Sie es, wiederholt das Browser-Layout auszulösen. Im Kontext von FLIP-Animationen bedeutet dies, das Durchlaufen von Elementen und das Lesen ihrer Position mit getBoundingClientRect zu vermeiden und sie dann sofort mit animate zu animieren. Batchieren Sie „Reads“ und „Writes“ wann immer möglich. Dies ermöglicht extrem flüssige Animationen.

Animation abbrechen

Versuchen Sie, zufällig auf die Quadrate im früheren Demo zu klicken, während sie sich bewegen, und dann erneut, nachdem sie angehalten haben. Sie werden Fehler sehen. Im wirklichen Leben interagieren Benutzer mit Elementen, während sie sich bewegen, daher lohnt es sich, sicherzustellen, dass sie reibungslos abgebrochen, pausiert und aktualisiert werden. 

Allerdings können nicht alle Animationen mit reverse umgekehrt werden. Manchmal möchten wir, dass sie stoppen und sich dann zu einer neuen Position bewegen (wie beim zufälligen Mischen einer Liste von Elementen). In diesem Fall müssen wir

  • die Größe/Position eines sich bewegenden Elements ermitteln
  • die aktuelle Animation beenden
  • die neuen Größen-/Positionsunterschiede berechnen
  • eine neue Animation starten

In React kann dies schwieriger sein, als es scheint. Ich habe viel Zeit damit verbracht, damit zu kämpfen. Das aktuelle Animations-Objekt muss gecacht werden. Eine gute Möglichkeit, dies zu tun, ist die Erstellung einer Map, um die Animation anhand einer ID abzurufen. Dann müssen wir die Größe und Position des sich bewegenden Elements ermitteln. Dafür gibt es zwei Möglichkeiten

  1. Verwenden Sie eine Funktionskomponente: Schleifen Sie einfach durch jedes animierte Element direkt im Körper der Funktion und cachen Sie die aktuellen Positionen.
  2. Verwenden Sie eine Klassenkomponente: Verwenden Sie die Lebenszyklusmethode getSnapshotBeforeUpdate.

Tatsächlich empfehlen die offiziellen React-Dokumente die Verwendung von getSnapshotBeforeUpdate, „da es zu Verzögerungen zwischen den Phasen ‚Render‘ (wie render) und ‚Commit‘ (wie getSnapshotBeforeUpdate und componentDidUpdate) kommen kann.“ Es gibt jedoch noch kein Hook-Gegenstück zu dieser Methode. Ich habe festgestellt, dass die Verwendung des Körpers der Funktionskomponente ausreichend ist.

Kämpfen Sie nicht gegen den Browser

Ich habe es schon einmal gesagt, aber vermeiden Sie es, gegen den Browser zu kämpfen, und versuchen Sie, die Dinge so zu machen, wie der Browser es tun würde. Wenn wir eine einfache Größenänderung animieren müssen, überlegen Sie, ob CSS ausreichen würde (z. B.  transform: scale()). Ich habe festgestellt, dass FLIP-Animationen am besten dort eingesetzt werden, wo Browser wirklich nicht helfen können

  • Änderungen der DOM-Position animieren (wie oben gezeigt)
  • Layout-Animationen teilen

Der zweite Punkt ist eine kompliziertere Version des ersten. Es gibt zwei DOM-Elemente, die sich wie eines verhalten und aussehen und ihre Position ändern (während ein anderes aus dem DOM entfernt/versteckt wird). Dieser Trick ermöglicht einige coole Animationen. Zum Beispiel wird diese Animation mit einer Bibliothek, die ich namens react-easy-flip erstellt habe, mit diesem Ansatz durchgeführt.

Bibliotheken

Es gibt eine ganze Reihe von Bibliotheken, die FLIP-Animationen in React erleichtern und den Boilerplate-Code abstrahieren. Zu den derzeit aktiv gepflegten gehören: react-flip-toolkit und meine, react-easy-flip.

Wenn Ihnen etwas Schwereres, aber für allgemeinere Animationen geeignetes nichts ausmacht, sehen Sie sich framer-motion an. Es bietet auch coole Shared-Layout-Animationen! Es gibt ein Video, das sich mit dieser Bibliothek beschäftigt.


Ressourcen und Referenzen