TL;DR
Podczas animowania klipów używaj przekształceń skali. Możesz zapobiec rozciąganiu i zniekształcaniu elementów podczas animacji, stosując skalowanie w przeciwnym kierunku.
Wcześniej publikowaliśmy informacje o tym, jak tworzyć efektywne efekty paralaksy i nieskończone scrollery. W tym artykule omówimy, co należy zrobić, aby uzyskać skuteczne animacje klipów. Jeśli chcesz zobaczyć prezentację, otwórz repozytorium GitHub z przykładowymi elementami interfejsu użytkownika.
Weźmy na przykład menu rozwijane:
Niektóre opcje tworzenia mają większą wydajność niż inne.
Złe rozwiązanie: animacja szerokości i wysokości elementu kontenera
Możesz użyć trochę kodu CSS, aby animować szerokość i wysokość elementu kontenera.
.menu {
overflow: hidden;
width: 350px;
height: 600px;
transition: width 600ms ease-out, height 600ms ease-out;
}
.menu--collapsed {
width: 200px;
height: 60px;
}
Bezpośrednim problemem związanym z tym podejściem jest to, że wymaga ono animowania elementów width
i height
.
Te właściwości wymagają obliczenia układu i wyświetlania wyników w każdej klatce animacji. Może to być bardzo kosztowne i zwykle powoduje brak 60 FPS. Jeśli to dla Ciebie nowość, przeczytaj nasze porady dotyczące wydajności renderowania, aby dowiedzieć się więcej o tym, jak działa proces renderowania.
Złe rozwiązanie: użyj właściwości CSS clip lub clip-path
Alternatywą dla animowania właściwości width
i height
może być użycie (obecnie przestarzałej) właściwości clip
do animowania efektu rozszerzania i zwijania. Możesz też użyć pola clip-path
. Użycie atrybutu clip-path
jest jednak mniej obsługiwane niż użycie atrybutu clip
. Funkcja clip
została wycofana. W prawo. Nie martw się. To rozwiązanie i tak nie było po Twojej myśli.
.menu {
position: absolute;
clip: rect(0px 112px 175px 0px);
transition: clip 600ms ease-out;
}
.menu--collapsed {
clip: rect(0px 70px 34px 0px);
}
Chociaż jest to lepsze rozwiązanie niż animowanie właściwości width
i height
elementu menu, ma ono tę wadę, że nadal powoduje wywołanie funkcji paint. Ponadto właściwość clip
, jeśli zdecydujesz się na ten sposób, wymaga, aby element, na którym działa, był umieszczony absolutnie lub sztywno, co może wymagać dodatkowych działań.
Dobre: skalowanie animacji
Ponieważ ten efekt polega na powiększaniu i pomniejszaniu obiektu, możesz użyć transformacji skali. To świetna wiadomość, ponieważ zmiana transformacji nie wymaga projektowania ani malowania, a przeglądarka może przekazać ją do procesora graficznego. Oznacza to, że efekt jest przyspieszony i znacznie bardziej prawdopodobne jest, że osiągnie 60 FPS.
Wadą tego podejścia, podobnie jak w przypadku większości elementów związanych z wydajnością renderowania, jest to, że wymaga ono nieco konfiguracji. Naprawdę warto!
Krok 1. Oblicz początkowy i końcowy stan
W przypadku podejścia, które wykorzystuje animacje skalowania, pierwszym krokiem jest odczytanie elementów, które informują o rozmiarze menu (zarówno po zwężeniu, jak i po rozwinięciu). W niektórych przypadkach nie można uzyskać obu tych informacji jednocześnie. Aby odczytać różne stany komponentu, trzeba na przykład przełączyć się między klasami.
Jeśli jednak musisz to zrobić, zachowaj ostrożność: getBoundingClientRect()
(lub offsetWidth
i offsetHeight
) powoduje, że przeglądarka przetwarza style i układy, jeśli zmieniły się one od ostatniego przetworzenia.
function calculateCollapsedScale () {
// The menu title can act as the marker for the collapsed state.
const collapsed = menuTitle.getBoundingClientRect();
// Whereas the menu as a whole (title plus items) can act as
// a proxy for the expanded state.
const expanded = menu.getBoundingClientRect();
return {
x: collapsed.width / expanded.width,
y: collapsed.height / expanded.height
};
}
W przypadku menu możemy założyć, że będzie ono mieć początkowo naturalną skalę (1, 1). Ta naturalna skala przedstawia rozszerzony stan, co oznacza, że będziesz musiał animować od wersji pomniejszonej (która została obliczona powyżej) do naturalnej skali.
Ale zaraz! Z pewnością można też dostosować zawartość menu, prawda? Tak, jak możesz zobaczyć poniżej.
Co możesz zrobić w tej sytuacji? Możesz zastosować do treści transformację odwrotną. Jeśli na przykład kontener jest zmniejszony do 1/5 normalnego rozmiaru, możesz zwiększyć rozmiar treści o 5 razy, aby zapobiec ich ściśnięciu. Należy pamiętać o 2 kwestiach:
Odwrotna transformacja to też operacja skalowania. Jest to dobre, ponieważ można je przyspieszyć tak samo jak animację w kontenerze. Może być konieczne zapewnienie elementom animowanym własnego poziomu kompozytora (aby umożliwić GPU pomoc). W tym celu możesz dodać do elementu
will-change: transform
lub, jeśli musisz obsługiwać starsze przeglądarki,backface-visiblity: hidden
.Odwrotna transformacja musi być obliczana dla każdej klatki. W tym miejscu sprawa może się skomplikować, ponieważ przy założeniu, że animacja jest w CSS i używa funkcji wygładzania, należy ją zrównoważyć podczas animacji przeciwnego przekształcenia. Obliczanie odwrotnej krzywej dla np.
cubic-bezier(0, 0, 0.3, 1)
nie jest jednak takie oczywiste.
Możesz więc zechcieć użyć animacji efektu za pomocą JavaScriptu. Możesz wtedy użyć równania łagodnego przejścia, aby obliczyć wartości skali i przeciwskali na każdy kadr. Wadą animacji opartych na JavaScript jest to, że gdy główny wątek (w którym działa JavaScript) jest zajęty innym zadaniem, Krótko mówiąc, animacja może się zacinać lub całkowicie zatrzymać, co nie wpływa korzystnie na UX.
Krok 2. Błyskawicznie twórz animacje CSS
Rozwiązaniem, które może na początku wydawać się dziwne, jest utworzenie animacji z kluczowymi klatkami za pomocą naszej funkcji wygładzania i wstrzyknięcie jej na stronę, aby mogła być używana przez menu. (wielkie podziękowania dla inżyniera Chrome Roberta Flacka za zwrócenie na to uwagi) Główną zaletą jest to, że animacja z kluczowymi klatkami, która zmienia transformacje, może być uruchamiana na kompilatorze, co oznacza, że nie jest ona zależna od zadań wykonywanych na głównym wątku.
Aby utworzyć animację klatki kluczowej, przechodzimy od 0 do 100 i obliczamy, jakie wartości skali będą potrzebne dla elementu i jego zawartości. Można je sprowadzić do ciągu znaków, który można wstrzyknąć na stronę jako element stylu. Wstrzyknięcie stylów spowoduje ponowne obliczenie stylów na stronie, co jest dodatkową pracą dla przeglądarki, ale będzie to zrobione tylko raz, gdy komponent się uruchamia.
function createKeyframeAnimation () {
// Figure out the size of the element when collapsed.
let {x, y} = calculateCollapsedScale();
let animation = '';
let inverseAnimation = '';
for (let step = 0; step <= 100; step++) {
// Remap the step value to an eased one.
let easedStep = ease(step / 100);
// Calculate the scale of the element.
const xScale = x + (1 - x) * easedStep;
const yScale = y + (1 - y) * easedStep;
animation += `${step}% {
transform: scale(${xScale}, ${yScale});
}`;
// And now the inverse for the contents.
const invXScale = 1 / xScale;
const invYScale = 1 / yScale;
inverseAnimation += `${step}% {
transform: scale(${invXScale}, ${invYScale});
}`;
}
return `
@keyframes menuAnimation {
${animation}
}
@keyframes menuContentsAnimation {
${inverseAnimation}
}`;
}
Osoby o nieograniczonej ciekawości mogą się zastanawiać, do czego służy funkcja ease()
w pętli for. Aby mapować wartości od 0 do 1 na ich wygładzone odpowiedniki, możesz użyć czegoś takiego:
function ease (v, pow=4) {
return 1 - Math.pow(1 - v, pow);
}
Możesz też wykonać wyszukiwanie w Google, aby zobaczyć, jak to wygląda. Handy! Jeśli potrzebujesz innych równań interpolacji, sprawdź Tween.js autorstwa Soledad Penadés, która zawiera ich całą masę.
Krok 3. Włącz animacje CSS
Po utworzeniu i zaimplementowaniu animacji w JavaScriptie ostatnim krokiem jest przełączenie klas, które umożliwiają animacje.
.menu--expanded {
animation-name: menuAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
.menu__contents--expanded {
animation-name: menuContentsAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
Spowoduje to uruchomienie animacji utworzonych w poprzednim kroku. Ponieważ wyrenderowane animacje są już wygładzone, funkcja timingu musi być ustawiona na linear
. W przeciwnym razie wygładzanie między poszczególnymi klatkami spowoduje, że animacja będzie wyglądać bardzo dziwnie.
Jeśli chodzi o zwijanie elementu, masz 2 opcje: zaktualizuj animację CSS, aby działała w odwrotnej kolejności. To zadziała, ale „odczucie” animacji będzie odwrotne, więc jeśli użyjesz łagodnego przejścia, odwrócenie będzie miało charakter łagodnego, co spowoduje wrażenie spowolnienia. Bardziej odpowiednim rozwiązaniem jest utworzenie drugiej pary animacji do zwijania elementu. Można je tworzyć w ten sam sposób co animacje z rozwiniętymi klatkami kluczowymi, ale z zamiennymi wartościami początkowymi i końcowymi.
const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;
Bardziej zaawansowana wersja: kółka
Możesz też użyć tej techniki do tworzenia animacji rozszerzania i zwijania w krąg.
Zasady są w dużej mierze takie same jak w poprzedniej wersji, w której skalujesz element i odwrotnie skalujesz jego bezpośrednie elementy podrzędne. W tym przypadku element, który jest powiększany, ma border-radius
50%, co powoduje, że jest okrągły, i jest otoczony przez inny element, który ma overflow: hidden
, co oznacza, że nie widzisz, jak koło wychodzi poza granice elementu.
Ostrzeżenie dotyczące tego konkretnego wariantu: podczas animacji w Chrome tekst jest rozmyty na ekranach o niskiej rozdzielczości DPI z powodu błędów zaokrągleń spowodowanych skalowaniem i odwrotnym skalowaniem tekstu. Jeśli chcesz dowiedzieć się więcej na ten temat, możesz oznaczyć gwiazdką i śledzić błąd.
Kod efektu otwierania koła znajdziesz w repozytorium GitHub.
Podsumowanie
Oto sposób na tworzenie wydajnych animacji klipów za pomocą transformacji skali. W idealnym świecie animacje klipów byłyby przyspieszone (jest na ten temat błąd w Chromium zgłoszony przez Jake'a Archibalda), ale dopóki tak się nie stanie, należy zachować ostrożność podczas animowania clip
lub clip-path
oraz unikać animowania width
lub height
.
Do takich efektów przydatne są też animacje internetowe, ponieważ mają interfejs API JavaScriptu, ale mogą działać na wątku kompozytora, jeśli animujesz tylko transform
i opacity
.
Niestety obsługa animacji internetowych nie jest zbyt dobra, ale możesz użyć ulepszeń progresywnych, jeśli są dostępne.
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
Dopóki to się nie zmieni, możesz używać bibliotek opartych na JavaScript do tworzenia animacji, ale lepszym rozwiązaniem może być wyrenderowanie animacji CSS i używanie jej zamiast animacji JavaScript. Jeśli Twoja aplikacja już korzysta z JavaScriptu do animacji, lepiej będzie, jeśli będziesz przynajmniej konsekwentny w swojej dotychczasowej bazie kodu.
Jeśli chcesz zobaczyć kod, który odpowiada za ten efekt, zajrzyj do repozytorium GitHub z przykładowymi elementami interfejsu użytkownika. Jak zawsze daj nam znać, jak Ci poszło, w komentarzach poniżej.