Animacje komponentów z Framer Motion 🚀
01/04/2023
6 min read
Dzisiaj chciałbym przedstawić Wam moje ulubione narzędzie do animacji w projektach React - Framer Motion! Jest to bardzo prosta i przyjemna do pracy biblioteka do pracy z animacjami
We wpisie tym przyjrzymy się bliżej Framer Motion, omówimy jego najważniejsze funkcje i nauczymy się, jak dodać piorunujące efekty do naszych aplikacji. Aby pokazać Wam, jak to wszystko działa, zbudujemy razem prosty, ale efektowny komponent Drawer, który doskonale sprawdzi się w wielu projektach.
A przede wszystkim - przekonamy się, że tworzenie animacji w React może być naprawdę proste i przyjemne. Do dzieła! 🎉
Animacje, interakcje i więcej: Poznaj Framer Motion
Framer Motion oferuje szeroki zakres możliwości, które pozwalają na tworzenie różnorodnych animacji i interakcji w aplikacjach React. Oto niektóre z nich:
- Animacje CSS: Umożliwia animowanie różnych właściwości CSS, takich jak kolor, wielkość, marginesy czy pozycję elementów.
- Drag and drop: Prosta implementacja mechanizmu "przeciągnij i upuść" dla elementów na stronie.
- Gesty: Obsługa gestów, takich jak przesunięcia czy przybliżania, co pozwala na tworzenie bardziej interaktywnych aplikacji.
- Spring physics: Zastosowanie fizyki sprężynowej do animacji, co pozwala na tworzenie bardziej realistycznych i płynnych efektów.
- Orchestration: Kontrolowanie kolejności i synchronizacji animacji wielu elementów, dzięki czemu możemy tworzyć złożone sceny.
- Variants: Tworzenie zdefiniowanych wariantów animacji dla różnych stanów, co ułatwia zarządzanie i kontrolowanie animacji.
- Shared layout animations: Ułatwienie animacji między różnymi komponentami, np. podczas zmiany stron w aplikacji.
- AnimatePresence: Łatwe dodawanie animacji wejścia i wyjścia dla komponentów, które pojawiają się i znikają z drzewa DOM.
- Server-side rendering (SSR) compatibility: Współpraca z renderowaniem po stronie serwera, co ułatwia tworzenie zoptymalizowanych aplikacji.
- Performance optimizations: Optymalizacja wydajności animacji, dzięki czemu działają one płynnie nawet na słabszych urządzeniach.
Dzięki tym funkcjom Framer Motion pozwala na tworzenie atrakcyjnych, płynnych i responsywnych animacji w aplikacjach React. W kolejnych częściach tego wpisu zastosujemy niektóre z tych możliwości, aby stworzyć drawer komponent z animacjami i gestami.
Konfiguracja projektu
Instalujemy bibliotekę standardowo za pomocą npm/yarn:
npm install framer-motion
Następnie tworzymy nowy komponent i piszemy strukturę HTML:
function Drawer() {const [open, setOpen] = useState(false);return (<>{open && (<divclassName="drawerOverlay"/>)}<divclassName='drawer'><divclassName='drawerInner'><DrawerHeader /><DrawerContent /></div></div></>);}
I stylizujemy trochę za pomocą CSS:
.drawer {font-family: 'Open Sans', sans-serif;position: fixed;top: calc(100% - 94px);width: 100%;left: 0;right: 0;height: 90%;z-index: 4;pointer-events: initial;}.drawerInner {height: 100%;}.drawerOverlay {width: 100%;height: 100vh;position: fixed;background-color: rgba(0, 0, 0, 0.3);top: 0;left: 0;z-index: 3;backdrop-filter: blur(0.4rem);pointer-events: initial;transition: all 0.3s ease-in-out;}
Mając tak przygotowaną podstawową strukturę, możemy przystąpić do animowania otwierania i zamykania drawera oraz obsługi gestu drag.
Dodanie animacji z framer motion
Zajmijmy się najpierw animacją overlaya. Założenie jest takie, że kiedy nasz komponent będzie otwarty, w tle pokażemy półprzeźroczysty wyblurowany overlay. Będzie on również obsługiwał zamknięcie drawera po kliknięciu na overlay. Element ten renderuje się dopiero, kiedy zmienna
open
będzie ustawiona na true
i tak samo usuwa się z DOM kiedy zamkniemy drawer. Jako że zamknięcie overlaya sprawia, że element znika z DOM, musimy zastosować tutaj <AnimatePresence>
.Jest to komponent dostarczany przez framer motion, który pozwala animować elementy, które znikają z DOM. Nie dalibyśmy rady tego zrobić za pomocą zwykłego CSS-a.
Overlayowi dodamy również drobną animację opacity. Aby to zrobić, musimy przygotować obiekt, który będzie zawierał konfiguracje animacji. W framer motion możemy to zrobić za pomocą
Variants
:const overlayVariants: Variants = {hidden: { opacity: 0 },visible: { opacity: 1 },};
Obiekt reprezentuje 2 stany, jakie przyjmie nasz overlay - opacity: 0 i opacity: 1. Dzięki temu uzyskamy efekt płynnego pojawiania się i znikania, zamiast skokowego.
<AnimatePresence>{open && (<motion.divclassName="drawerOverlay"initial="hidden"animate="visible"exit="hidden"onClick={handleCloseDrawer}variants={overlayVariants}/>)}</AnimatePresence>
Całość musimy owrapować wspomnianym wcześniej
<AnimatePresence/>
. Następnie div
zamieniamy na strukturę <motion.div
, dzięki czemu otrzymamy funkcjonalności framer motion. Do propsów initial
, animate
i exit
musimy przypisać odpowiednie animacje. Animacje te zostaną pobrane z obiektu overlayVariants
, który musimy przekazać do propa variants
. Poszczególne propsy odpowiadają za:initial
- stan początkowy komponentuanimate
- jakie własności elementu mają zostać zanimowane - w naszym przypadku opacity ma zostać zanimowane od wartości 0 do wartości 1exit
- jaka animacja ma zostać odpalona na usunięcie elementu z DOM. W naszym przypadku będzie to animacja ze stanuvisible
dohidden
Tyle jest wystarczające, aby otrzymać ładną animację pojawiania się i znikania overlaya.
Przejdźmy teraz do głównej animacji otwierania drawera.
Musimy stworzyć kolejny obiekt typu
Variants
, tym razem dla animacji otwierania i zamykania drawera:const variants: Variants = {inactive: { y: 0 },active: { y: '-80%' },};
W Drawerze animowana będzie pozycja. Na początku na ekranie widoczny będzie przycisk do otwarcia drawera i kawałek treści, a po otwarciu zmienimy wartość
y
komponentu, aby był widoczny na 80% całej strony.Równocześnie chcemy, aby drawer otwierał i zamykał się na gest pociągnięcia w górę/pociągnięcia w dół. Musimy zatem stworzyć funkcję, która będzie rozpoznawała w jaki sposób użytkownik wykonał gest drag:
const handleDragEnd = (_: unknown, info: PanInfo): void => {if (info.offset.y < 0 && info.offset.y < -80) {setOpen(true);}if (info.offset.y > 0 && info.offset.y > 80) {setOpen(false);}};
W obiekcie
info
znajdują się wszystkie potrzebne dane dotyczące wykonanego gestu. Funkcja nazywa się handleDragEnd
, ponieważ musimy obsłużyć tylko koniec wykonania gestu. Za pomocą info.offset
wiemy dokładnie o jakie wartości drawer został przesunięty. Nie chcemy, aby za każdym gestem drawer się otwierał bądź zamykał, dlatego dodajemy bufor w postaci 80px
, czyli jeżeli użytkownik przesunął za pomocą drag drawera o więcej niż 80px, to znaczy, że chce otworzyć bądź zamknąć.Taka konfiguracja jest wystarczająca, aby móc zaktualizować naszą strukturę HTML:
<motion.divclassName={'drawer'}dragElastic={0.8}onDragEnd={handleDragEnd}dragConstraints={{ top: 0, bottom: 0 }}drag="y"><motion.divclassName={'drawerInner'}transition={{ type: 'spring', duration: 0.8 }}variants={variants}initial="inactive"animate={open ? 'active' : 'inactive'}><DrawerHeader /><div className='drawer-content'>{/* Placeholder for content */}</div></motion.div></motion.div>
Omówiliśmy wcześniej już propsy typu
variants
, initial
i animate
, więc tutaj skupimy się bardziej na obsłudze drag. Jak można zauważyć, odpowiada za to kilka wartości:drag="y"
- definiuje nam, w którą stronę użytkownik może przeciągać. U nas tylko w pionie (góra-dół)dragConstraints
- definiuje nam granice, za jakie element może zostać przeciagnięty. My blokujemy w taki sposób, aby nie można było wyjść poza górę i dół.onDragEnd
- metoda odpalająca się, kiedy użytkownik skończy wykonywać gest - tutaj przekazujemy wcześniej zdefiniowaną funkcjędragElastic
itransition
- dodatkowe parametry animacji. Zachęcam do zabawy z nimi!
Rezultat końcowy
I to by było na tyle. Jak widać, nie potrzeba dużo skomplikowanego kodu, aby uzyskać ładną, płynną i wydajną animację. Oto prezentuje się efekt końcowy:
Cały projekt dostępny jest na githubie: https://github.com/dawidkostrzewa/drawer-framer-motion