Orkiestrowanie złożoności za pomocą interfejsu API animacji internetowych

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Istnieje zbyt wiele opcji w Web Animations API, aby móc je tak łatwo wybrać. Nauczenie się, jak działa chronometraż i jak kontrolować odtwarzanie kilku animacji jednocześnie, stanowi solidną podstawę, na której można oprzeć swoje projekty.

Nie ma kompromisu między prostymi przejściami a złożonymi animacjami. Albo jesteś w porządku z tym, co zapewniają przejścia i animacje CSS, albo nagle potrzebujesz całej mocy, jaką możesz uzyskać. Web Animations API udostępnia wiele narzędzi do pracy z animacjami. Ale musisz wiedzieć, jak sobie z nimi radzić. W tym artykule omówimy główne punkty i techniki , które mogą pomóc w radzeniu sobie ze złożonymi animacjami przy zachowaniu elastyczności.

Zanim zagłębimy się w ten artykuł, ważne jest, abyś zapoznał się z podstawami API Web Animations i JavaScript. Aby było to jasne i uniknąć odwrócenia uwagi od problemu, podane przykłady kodu są proste. Nie będzie nic bardziej złożonego niż funkcje i obiekty. Jako dobry punkt wyjścia do samych animacji sugerowałbym MDN jako ogólne odniesienie, doskonałą serię Daniela C. Wilsona oraz CSS Animations vs Web Animations API autorstwa Olliego Williamsa. Nie będziemy przechodzić przez sposoby definiowania efektów i dostrajania ich, aby osiągnąć pożądany rezultat. W tym artykule założono, że masz zdefiniowane animacje i potrzebujesz pomysłów i technik do ich obsługi.

Zaczynamy od przeglądu interfejsów i ich przeznaczenia. Następnie przyjrzymy się terminom i poziomom kontroli, aby określić co, kiedy i jak długo. Następnie nauczymy się traktować kilka animacji jako jedną, zawijając je w obiekty. To byłby dobry początek Twojej drogi do korzystania z Web Animations API.

Interfejsy

Web Animations API daje nam nowy wymiar kontroli. Wcześniej CSS Transitions and Animation, zapewniając potężny sposób definiowania efektów, nadal miał jeden punkt aktywacji . Jak włącznik światła, albo był włączony, albo wyłączony. Możesz bawić się funkcjami opóźnień i wygładzania, aby stworzyć dość złożone efekty. Jednak w pewnym momencie staje się niewygodne i trudne w obsłudze.

Web Animations API zamienia ten pojedynczy punkt aktywacji w pełną kontrolę nad odtwarzaniem . Włącznik światła zamienia się w ściemniacz z suwakiem. Jeśli chcesz, możesz przekształcić go w cały inteligentny dom, ponieważ oprócz sterowania odtwarzaniem możesz teraz definiować i zmieniać efekty w czasie wykonywania. Możesz teraz dostosować efekty do kontekstu lub zaimplementować edytor animacji z podglądem w czasie rzeczywistym.

Zaczynamy od interfejsu animacji. Aby uzyskać obiekt animacji, możemy użyć metody Element.animate . Dajesz mu klatki kluczowe i opcje, a od razu odtwarza twoją animację. Zwraca również instancję obiektu Animation . Jego celem jest sterowanie odtwarzaniem.

Pomyśl o tym jak o magnetofonie , jeśli je pamiętasz. Zdaję sobie sprawę, że niektórzy czytelnicy mogą nie wiedzieć, co to jest. Nieuniknione jest, że każda próba zastosowania pojęć ze świata rzeczywistego do opisania abstrakcyjnych rzeczy komputerowych szybko się rozpadnie. Ale niech cię uspokoi — czytelnika, który nie zna radości przewijania taśmy ołówkiem — że ludzie, którzy wiedzą, czym jest magnetofon, pod koniec tego artykułu będą jeszcze bardziej zdezorientowani.

Wyobraź sobie pudełko. Posiada szczelinę, do której trafia kaseta oraz przyciski do odtwarzania, zatrzymywania i przewijania. Tym właśnie jest instancja interfejsu animacji — pudełko, które przechowuje zdefiniowaną animację i zapewnia sposoby interakcji z jej odtwarzaniem. Dajesz mu coś do zabawy, a to daje ci z powrotem kontrolę.

Otrzymane elementy sterujące są wygodnie podobne do tych, które otrzymujesz z elementów audio i wideo. Są to metody odtwarzania i pauzy oraz właściwość bieżącego czasu . Dzięki tym trzem kontrolkom możesz zbudować wszystko, jeśli chodzi o odtwarzanie.

Sama kaseta jest pakietem zawierającym odniesienie do animowanego elementu, definicję efektów oraz opcje, które obejmują między innymi taktowanie. I tym właśnie jest KeyframeEffect . Nasza kaseta magnetofonowa to coś, co przechowuje wszystkie nagrania i informacje o długości nagrań. Dopasowanie wszystkich tych właściwości do elementów fizycznej kasety pozostawiam wyobraźni starszego odbiorcy. Pokażę ci, jak to wygląda w kodzie.

Kiedy tworzysz animację za pomocą Element.animate , używasz skrótu, który wykonuje trzy rzeczy. Tworzy instancję KeyframeEffect . Umieszcza się w nowej instancji Animation . Natychmiast zaczyna grać.

 const animation = element.animate(keyframes, options);

Rozłóżmy to i zobaczmy równoważny kod, który robi to samo.

 const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)

Weź kasetę (1), włóż ją do odtwarzacza (2), a następnie naciśnij przycisk Play (3).

Wiedza o tym, jak to działa za kulisami, polega na tym, aby móc oddzielić definicję klatek kluczowych i zdecydować, kiedy je odtworzyć. Kiedy masz dużo animacji do skoordynowania, pomocne może być zebranie ich wszystkich w pierwszej kolejności, aby wiedzieć, że są gotowe do gry. Generowanie ich w locie i nadzieja, że ​​zaczną grać we właściwym momencie, nie jest czymś, na co chciałbyś mieć nadzieję. Zbyt łatwo przełamać pożądany efekt przeciągnięciem kilku klatek. W przypadku długiej sekwencji opór się kumuluje, czego wynikiem nie jest wcale przekonujące doświadczenie.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

wyczucie czasu

Podobnie jak w komedii, w animacjach liczy się czas. Aby efekt zadziałał, aby uzyskać określone odczucie, musisz umieć dostroić sposób, w jaki zmieniają się właściwości. W interfejsie Web Animations API można kontrolować dwa poziomy taktowania .

Na poziomie poszczególnych nieruchomości mamy offset . Przesunięcie zapewnia kontrolę nad czasem pojedynczej właściwości . Nadając mu wartość od zera do jednego, określasz, kiedy uruchamia się każdy efekt. Gdy zostanie pominięty, równa się zeru.

Być może pamiętasz z @keyframes w CSS, jak używać procentów zamiast from / to . To jest offset , ale podzielony przez sto. Wartość offset jest częścią czasu trwania pojedynczej iteracji .

offset umożliwia rozmieszczenie klatek kluczowych w ramach KeyframeEffect . Przesunięcie liczby względnej gwarantuje, że bez względu na czas trwania lub szybkość odtwarzania wszystkie klatki kluczowe zaczynają się w tym samym momencie względem siebie.

Jak wspomnieliśmy wcześniej, offset to część czasu trwania . Teraz chcę, żebyś uniknęła moich błędów i straty czasu na tym. Ważne jest, aby zrozumieć, że czas trwania animacji to nie to samo, co ogólny czas trwania animacji. Zwykle są takie same i to może cię zmylić, a mnie zdecydowanie zdezorientować.

Czas trwania to czas w milisekundach potrzebny do zakończenia jednej iteracji. Domyślnie będzie równa całkowitemu czasowi trwania. Po dodaniu opóźnienia lub zwiększeniu liczby iteracji w czasie trwania animacji przestaje wskazywać liczbę, którą chcesz poznać. Ważne jest, aby zrozumieć, jak wykorzystać to na swoją korzyść.

Jeśli chcesz skoordynować odtwarzanie klatki kluczowej w większym kontekście, takim jak odtwarzanie multimediów, musisz użyć opcji czasu. Cały czas trwania animacji od początku do „zakończenia” zdarzenia w następującym równaniu:

 delay + (iterations × duration) + end delay

Możesz to zobaczyć w akcji w poniższym demo:

Zobacz pióro [Jaki jest rzeczywisty czas trwania animacji?](https://codepen.io/smashingmag/pen/VwWWrzz) autorstwa Kirilla Myshkina.

Zobacz pióro Jaki jest rzeczywisty czas trwania animacji? autorstwa Kirilla Myszkina.

To, co pozwala nam to zrobić, to wyrównać kilka animacji w kontekście mediów o stałej długości. Zachowując pożądany czas trwania animacji w stanie nienaruszonym, możesz „dopełnić” ją delay na początku i delayEnd na końcu, aby osadzić ją w kontekście o dłuższym czasie trwania. Jeśli o tym pomyślisz, delay w tym sensie działałoby tak samo, jak przesunięcie w klatkach kluczowych. Pamiętaj tylko, że opóźnienie jest ustawione w milisekundach, więc możesz chcieć przekonwertować je na wartość względną.

Jeszcze jedna opcja czasu, która pomogłaby wyrównać animację, to iterationStart . Ustawia pozycję początkową iteracji. Weź demo kuli bilardowej. Dostosowując suwak iterationStart , możesz ustawić pozycję początkową piłki i obrót, na przykład możesz ustawić, aby zaczynała skakać od środka ekranu i aby liczba była prosta w aparacie w ostatniej klatce.

Zobacz pióro [Tweak interationStart](https://codepen.io/smashingmag/pen/qBjjVPR) autorstwa Kirilla Myshkina.

Zobacz interakcję z Pen Tweak Zacznij od Kirilla Myshkina.

Kontroluj kilka jako jeden

Kiedy pracowałem nad edytorem animacji do aplikacji prezentacyjnej, musiałem rozmieścić kilka animacji dla pojedynczego elementu na osi czasu. Moją pierwszą próbą było użycie offset , aby umieścić moją animację we właściwym punkcie początkowym na osi czasu.

Szybko okazało się to niewłaściwym sposobem użycia offset . Jeśli chodzi o ten konkretny interfejs użytkownika, animacja poruszania się na osi czasu oznacza zmianę pozycji początkowej bez zmiany czasu trwania animacji. Z offset oznaczało to, że musiałem zmienić kilka rzeczy, samo offset , a także zmienić offset właściwości zamykania, aby upewnić się, że czas trwania się nie zmieni. Rozwiązanie okazało się zbyt skomplikowane, aby je zrozumieć.

Drugi problem dotyczył właściwości transform . Ze względu na to, że może przedstawiać kilka charakterystycznych zmian w elemencie, może być trudno sprawić, by robił to, co chcesz. W przypadku chęci zmiany tych właściwości niezależnie od siebie, może to być jeszcze trudniejsze. Zmiana funkcji wagi ma wpływ na wszystkie następujące po niej funkcje. Oto dlaczego tak się dzieje.

Właściwość Transform może przyjmować kilka funkcji w sekwencji jako wartość. W zależności od kolejności funkcji wynik się zmienia. Weź scale i translate . Czasami przydatne jest określenie translate w procentach, czyli w stosunku do rozmiaru elementu. Powiedzmy, że chcesz, aby piłka skoczyła dokładnie na wysokość trzech własnych średnic. Teraz w zależności od tego, gdzie umieścisz funkcję skali — przed lub po translate — wynik zmienia się z trzech wysokości oryginalnego rozmiaru lub przeskalowanego.

Jest to ważna cecha właściwości transform . Potrzebujesz go, aby osiągnąć dość złożoną transformację. Ale kiedy potrzebujesz, aby te przekształcenia były odrębne i niezależne od innych przekształceń elementu, staje to na twojej drodze.

Zdarzają się sytuacje, w których nie można umieścić wszystkich efektów w jednej właściwości transform . Dość szybko może być za dużo. Zwłaszcza jeśli twoje klatki kluczowe pochodzą z różnych miejsc, będziesz potrzebował bardzo złożonego scalania przekształconego ciągu . Trudno polegać na automatycznym mechanizmie, ponieważ logika nie jest prosta. Ponadto może być trudno zrozumieć, czego się spodziewać. Aby to uprościć i zachować elastyczność, musimy je rozdzielić na różne kanały.

Jednym z rozwiązań jest zawinięcie naszych elementów w div , z których każdy może być animowany osobno, np. div do pozycjonowania na płótnie, inny do skalowania, a trzeci do obracania. W ten sposób nie tylko znacznie uprościsz definicję animacji, ale także otworzysz możliwość definiowania różnych źródeł transformacji tam, gdzie ma to zastosowanie.

Mogłoby się wydawać, że dzięki tej sztuczce sprawy wymykają się spod kontroli. Że mnożymy ilość problemów, które mieliśmy wcześniej. W rzeczywistości, kiedy pierwszy raz znalazłem tę sztuczkę, odrzuciłem ją jako za dużo. Pomyślałem, że mógłbym po prostu upewnić się, że moja właściwość transform jest skompilowana ze wszystkich kawałków we właściwej kolejności w jednym kawałku. Potrzeba było jeszcze jednej funkcji transform , aby uczynić rzeczy zbyt skomplikowanymi do zarządzania, a pewnych rzeczy niemożliwymi do wykonania. Mój kompilator ciągów właściwości transform zaczął zabierać coraz więcej czasu, aby działać poprawnie, więc zrezygnowałem.

Okazało się, że sterowanie odtwarzaniem kilku animacji nie jest takie trudne, jak mogłoby się początkowo wydawać. Pamiętasz analogię z magnetofonem od początku? Co by było, gdybyś mógł użyć własnego odtwarzacza, który mieści dowolną liczbę kaset? Co więcej, możesz dodać tyle przycisków, ile chcesz na tym odtwarzaczu.

Jedyna różnica między wywoływaniem play pojedynczej animacji a szeregiem animacji polega na tym, że musisz wykonać iterację. Oto kod, którego możesz użyć dla dowolnej metody instancji Animation :

 // To play just call play on all of them animations.forEach((animation) => animation.play());

Wykorzystamy to do stworzenia wszelkiego rodzaju funkcji dla naszego odtwarzacza.

Stwórzmy to pudełko, w którym będą przechowywane animacje i odtwarzane. Możesz tworzyć te pudełka w dowolny sposób. Aby było jasne, pokażę ci przykład wykonania tego za pomocą funkcji i obiektu. Funkcja createPlayer pobiera szereg animacji, które mają być odtwarzane synchronicznie. Zwraca obiekt za pomocą jednej metody play .

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }

To wystarczy, abyś wiedział, aby zacząć rozszerzać funkcjonalność. Dodajmy metody pauzy i currentTime .

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }

createPlayer z tymi trzema metodami zapewnia wystarczającą kontrolę, aby zaaranżować dowolną liczbę animacji . Ale pchnijmy to trochę dalej. Zróbmy to tak, aby nasz odtwarzacz mógł zabierać nie tylko dowolną ilość kaset, ale także inne odtwarzacze.

Jak widzieliśmy wcześniej, interfejs Animation jest podobny do interfejsów multimedialnych. Korzystając z tego podobieństwa, możesz umieścić w odtwarzaczu różne rzeczy. Aby to uwzględnić, dostosujmy metodę currentTime , aby działała zarówno z obiektami animacji, jak i obiektami pochodzącymi z createPlayer .

 function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }

Odtwarzacz, który właśnie stworzyliśmy, pozwala ukryć złożoność kilku elementów div dla jednoelementowych kanałów animacji. Te elementy można by zgrupować w scenie. A każda scena może być częścią czegoś większego. Wszystko, co można było zrobić tą techniką.

Aby zademonstrować demo timingu, podzieliłem wszystkie animacje na trzech graczy. Pierwszym z nich jest sterowanie odtwarzaniem podglądu po prawej stronie. Druga łączy animację skoków wszystkich konturów kulek po lewej i tej w podglądzie.

Wreszcie trzeci to gracz, który połączył animacje pozycji kulek w lewym pojemniku. Ten gracz pozwala kulkom rozprzestrzeniać się w ciągłej demonstracji animacji z około 60 klatkami na sekundę.

Wniosek

Interfejsy internetowe, takie jak Web Animations API, ujawniają nam pewne rzeczy, które przeglądarki robiły przez cały czas. Przeglądarki wiedzą, jak szybko renderować, przekazując pracę do GPU. Dzięki Web Animations API mamy nad tym kontrolę. Nawet jeśli ta kontrola może wydawać się nieco obca lub myląca, nie oznacza to, że używanie jej również powinno być mylące. Dzięki zrozumieniu kontroli czasu i odtwarzania, masz narzędzia do oswajania tego interfejsu API do swoich potrzeb. Powinieneś być w stanie określić, jak złożone powinno być.

Dalsza lektura

  • „Praktyczne techniki projektowania animacji”, Sarah Drasner
  • „Projektowanie ze zredukowanym ruchem dla wrażliwości na ruch”, Val Head
  • „Alternatywny interfejs głosowy dla asystentów głosowych”, Ottomatias Peura
  • „Projektowanie lepszych podpowiedzi dla mobilnych interfejsów użytkownika” — Eric Olive