Eines der Dinge, die uns bei der Erstellung unserer neuen Website verwirrt haben, waren die Morphing-Wellen, die Sie an verschiedenen Stellen auf den Seiten sehen können. Wir hatten viele Ideen, wie man sie ohne großen Aufwand richtig implementieren könnte. Die meisten Lösungen waren jedoch langsam und wir mussten von Grund auf etwas entwickeln, das schneller war als die bereits vorhandenen Bibliotheken.
Lösungsvorschläge
Wir begannen mit einem einfachen SVG-Objekt, das mit verschiedenen Bibliotheken animiert wurde. Da wir 3 Objekte auf einer Seite haben wollten, war das Ergebnis nicht sehr zufriedenstellend. Alle Animationen waren einfach zu langsam - alle Pfade eines einzigen SVG-Objekts mussten in sehr kurzen Zeitabständen aktualisiert werden, was die gesamte Seite so langsam wie eine Schnecke machte. Wir mussten die Lösung mit reinem SVG, das in ein Dokument eingefügt wurde, verwerfen. Damit blieben uns zwei andere Lösungen zur Auswahl.
Die Video
Element war die zweite Option. Wir begannen mit zwei Problemen:
- transparenten Hintergrund, der bei den gängigsten Videoformaten wie .mp4 oder .webm nicht angewendet werden kann,
- Reaktionsfähigkeit, was ein echtes Problem war, da Videos als solche nicht skalierbar sind.
Wir beschlossen, diese Lösung auf die lange Bank zu schieben - "wenn wir nichts anderes finden, entscheiden wir uns für diese Lösung".
Die letzte Option war die Verwendung von Leinwand
mit WebGL
Rendering. Das war eine sehr ungewöhnliche Option, weil wir die gesamte Rendering-Mechanik selbst entwickeln mussten. Das liegt daran, dass die morphischen Wellen, die wir haben, benutzerdefiniert waren - das zwang uns, eine benutzerdefinierte Lösung zu entwerfen, und das war die Option, die wir verfolgen und auf die wir uns wirklich konzentrieren wollten.
Architektur der Lösung
Fangen wir von vorne an. Was war das Material, das wir verwenden mussten, um diese Wellen zu bauen? Die Idee war, dass alle Wellen eine SVG-Datei der Größe 1×1 und bestimmte Pfade um diesen Bereich positioniert waren. Die Animation dieser SVG-Datei wurde durch einige Formen von Zuständen in dieser Datei erstellt. Alle Animationen wurden also als eine Reihe von Dateien dargestellt, die die Phasen der Bewegung einer Form enthielten.
Sehen Sie sich genauer an, was die Zustände sind - alle Pfade sind nur eine Art Array mit bestimmten Werten, die in einer bestimmten Reihenfolge angeordnet sind. Wenn man diese Werte an bestimmten Positionen innerhalb dieses Arrays ändert, verändert sich die Form in ihren spezifischen Teilen. Wir können dies anhand des folgenden Beispiels vereinfachen:
Zustand 1: [1, 50, 25, 40, 100]
Zustand 2: [0, 75, -20, 5, 120]
Zustand 3: [5, 0, -100, 80, 90]
Wir können also davon ausgehen, dass die Form, die wir rendern wollen, aus einem Array mit 5 Elementen besteht, die sich mit dem linearen Easing in bestimmten Zeitabschnitten verändern. Wenn die Animation die letzte Stufe beendet hat, beginnt sie wieder mit der ersten, um den Eindruck einer unendlichen Animation zu vermitteln.
Aber... Moment mal - was genau ist das oben dargestellte Array? Wie ich bereits erwähnt habe, handelt es sich um einen Pfad, der für die Anzeige einer bestimmten Form verantwortlich ist. Der ganze Zauber ist in der d
Eigenschaft von SVGs Pfad. Diese Eigenschaft enthält eine Reihe von "Befehlen" zum Zeichnen einer Form und jeder Befehl hat eine Reihe von Argumenten. Das oben erwähnte Array besteht aus allen Werten (Argumenten), die mit diesen Befehlen verbunden sind.
Der einzige Unterschied zwischen diesen "Zustandsdateien" waren die Werte bestimmter Befehle, da die Reihenfolge der Befehle die gleiche war. Der ganze Zauber bestand also darin, alle Werte zu erhalten und sie zu animieren.
Der Assistent namens Physik
Im obigen Absatz habe ich erwähnt, dass die einzige Magie bei der Animation eines Objekts die Schaffung von Übergängen zwischen allen Phasen einer Form ist. Die Frage ist: Wie kann man das mit Canvas machen?
Die Funktion, die jeder, der mit Leinwand
wissen sollte, ist requestAnimationFrame. Wenn Sie diese Funktion zum ersten Mal sehen, sollten Sie unbedingt damit beginnen, dies zu lesen. Das, was uns an dieser Funktion interessiert, ist das Argument - DOMHighResTimeStamp
. Es sieht wirklich erschreckend aus, aber in der Praxis ist es nicht so schwer zu verstehen. Wir können sagen, dass es ein Zeitstempel der verstrichenen Zeit seit dem ersten Rendering ist.
Ok, aber was können wir damit anfangen? Da die requestAnimationFrame
Funktion rekursiv aufgerufen werden soll, können wir auf ein Zeitdelta zwischen ihren Aufrufen zugreifen. Und schon geht's los mit der Wissenschaft! ⚛️ (ok, vielleicht keine Raketenwissenschaft... noch nicht)
Die Physik lehrt uns, dass das Delta einer Entfernung gleich dem Delta der Zeit multipliziert mit der Geschwindigkeit ist. In unserem Fall ist die Geschwindigkeit konstant, weil wir den Endpunkt in einer bestimmten Zeit erreichen wollen. Schauen wir uns also an, wie wir dies mit den oben genannten Zuständen darstellen können:
Angenommen, wir wollen innerhalb von eintausend Millisekunden zwischen diesen Zuständen wechseln, dann lauten die Geschwindigkeitswerte wie folgt:
delta: [ -1, 25, -45, -35, 20]
Geschwindigkeit: [-1/1000, 25/1000, -45/1000, -35/1000, 20/1000]
Die Geschwindigkeit oben sagt uns: für jede Millisekunde erhöhen wir den Wert um -1/1000. Und hier ist der Punkt, an dem wir wieder zu unserer requestAnimationFrame
und Zeit delta. Der Wert einer bestimmten Position, um den wir uns erhöhen wollen, ist das Zeitdelta multipliziert mit der Geschwindigkeit seiner Position. Ein weiterer Punkt, der problemlos erreicht werden kann, ist die Begrenzung des Wertes, damit er den Zustand, in den er übergeht, nicht überschreitet.
Wenn der Übergang endet, rufen wir einen weiteren auf und so weiter. Und es scheint nicht so schwer zu sein, dies zu implementieren, aber eine der wichtigsten Regeln in Software-Entwicklung ist es, keine Zeit auf Dinge zu verwenden, die bereits umgesetzt sind. Also - wir haben eine kleine Bibliothek die es uns ermöglicht, diese Übergänge mühelos zu gestalten.
So haben wir eine animierte Welle geschaffen, die wie ein lebendiges Wesen aussieht.
Ein paar Worte zum Klonen von Formen
Wie Sie sehen können, sind die Wellen der Marke Codest nicht eine einzige animierte Figur. Sie bestehen aus vielen Figuren mit der gleichen Form, aber einer anderen Größe und Position. In diesem Schritt werden wir einen kurzen Blick darauf werfen, wie man sie auf diese Weise dupliziert.
Der Canvas-Kontext ermöglicht es uns also maßstabsgerechte Zeichenfläche (unter der Haube - wir können sagen, dass es alle Dimensionen in drawable Methoden mit "k" multipliziert, wobei "k" ist ein Skalierungsfaktor, standardmäßig auf "1" gesetzt), Leinwand übersetzen lassenEs ist, als würde man den Ankerpunkt einer Zeichenfläche ändern. Und wir können mit diesen Methoden auch zwischen diesen Zuständen springen: speichern und wiederherstellen.
Mit diesen Methoden können wir den Zustand "keine Änderungen" speichern und dann eine bestimmte Anzahl von Wellen in der Schleife mit korrekt skalierter und übersetzter Leinwand rendern. Gleich danach können wir zum gespeicherten Zustand zurückkehren. Das ist alles über das Klonen von Figuren. Viel einfacher als das Klonen von Schafen, nicht wahr?
Kirsche oben drauf
Ich habe erwähnt, dass wir eine der möglichen Lösungen wegen der Leistung verworfen haben. Die Option mit Canvas ist ziemlich schnell, aber niemand hat gesagt, dass sie nicht noch weiter optimiert werden könnte. Beginnen wir mit der Tatsache, dass wir uns nicht wirklich um den Übergang von Formen kümmern, wenn sie sich außerhalb des Browser-Viewports befinden.
Es gibt eine weitere Browser-API, die Programmierer lieben - IntersectionObserver. Es erlaubt uns, bestimmte Elemente der Seite zu verfolgen und Ereignisse zu behandeln, die aufgerufen werden, wenn diese Elemente erscheinen oder aus dem Viewport verschwinden. Im Moment - wir haben eine ziemlich einfache Situation - lassen Sie uns den Zustand der Sichtbarkeit erstellen, ändern Sie es durch IntersectionObserver Event-Handler und schalten Sie einfach das Rendering-System ein / aus für bestimmte Formen. Und ... boom, die Leistung hat sich stark verbessert! Wir rendern nur noch die Dinge, die im Viewport sichtbar sind.
Zusammenfassung
Die Entscheidung, wie etwas umgesetzt werden soll, fällt oft schwer, vor allem wenn die verfügbaren Optionen ähnliche Vor- und Nachteile zu haben scheinen. Der Schlüssel zur richtigen Entscheidung liegt darin, jede einzelne Lösung zu analysieren und diejenigen auszuschließen, die wir als weniger vorteilhaft ansehen. Nicht alles ist eindeutig - eine Lösung erfordert mehr Zeit als die anderen, ist aber vielleicht einfacher zu optimieren oder besser anpassbar.
Obwohl fast im Minutentakt neue JS-Bibliotheken auftauchen, gibt es Dinge, die sie nicht lösen können. Und deshalb sollte jeder Frontend-Ingenieur (und nicht nur er) die Browser-APIs kennen, sich über technische Neuigkeiten auf dem Laufenden halten und manchmal einfach denken: "Wie würde meine Implementierung dieser Bibliothek aussehen, wenn ich das tun müsste?". Mit mehr Wissen über Browser können wir wirklich ausgefallene Dinge bauen, gute Entscheidungen über die von uns verwendeten Tools treffen und mehr Selbstvertrauen in unsere Code.
Lesen Sie mehr:
– Ruby 3.0. Ruby und weniger bekannte Methoden zur Kontrolle der Privatsphäre
– Halt die Klappe und nimm dein Geld #1: Versteckte Kosten und echte Agilität im Produktentwicklungsprozess
– CTO-Herausforderungen - Skalierung und Wachstum von Softwareprodukten