Animacje komponentów z Framer Motion 🚀

01/04/2023
6 min read
Animacje komponentów z Framer Motion 🚀

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:

  1. Animacje CSS: Umożliwia animowanie różnych właściwości CSS, takich jak kolor, wielkość, marginesy czy pozycję elementów.
  2. Drag and drop: Prosta implementacja mechanizmu "przeciągnij i upuść" dla elementów na stronie.
  3. Gesty: Obsługa gestów, takich jak przesunięcia czy przybliżania, co pozwala na tworzenie bardziej interaktywnych aplikacji.
  4. Spring physics: Zastosowanie fizyki sprężynowej do animacji, co pozwala na tworzenie bardziej realistycznych i płynnych efektów.
  5. Orchestration: Kontrolowanie kolejności i synchronizacji animacji wielu elementów, dzięki czemu możemy tworzyć złożone sceny.
  6. Variants: Tworzenie zdefiniowanych wariantów animacji dla różnych stanów, co ułatwia zarządzanie i kontrolowanie animacji.
  7. Shared layout animations: Ułatwienie animacji między różnymi komponentami, np. podczas zmiany stron w aplikacji.
  8. AnimatePresence: Łatwe dodawanie animacji wejścia i wyjścia dla komponentów, które pojawiają się i znikają z drzewa DOM.
  9. Server-side rendering (SSR) compatibility: Współpraca z renderowaniem po stronie serwera, co ułatwia tworzenie zoptymalizowanych aplikacji.
  10. 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:

1function Drawer() {
2 const [open, setOpen] = useState(false);
3
4 return (
5 <>
6 {open && (
7 <div
8 className="drawerOverlay"
9 />
10 )}
11 <div
12 className='drawer'
13 >
14 <div
15 className='drawerInner'
16 >
17 <DrawerHeader />
18 <DrawerContent />
19 </div>
20 </div>
21 </>
22 );
23}
24

I stylizujemy trochę za pomocą CSS:

1.drawer {
2 font-family: 'Open Sans', sans-serif;
3 position: fixed;
4 top: calc(100% - 94px);
5 width: 100%;
6 left: 0;
7 right: 0;
8 height: 90%;
9 z-index: 4;
10 pointer-events: initial;
11}
12
13.drawerInner {
14 height: 100%;
15}
16
17.drawerOverlay {
18 width: 100%;
19 height: 100vh;
20 position: fixed;
21 background-color: rgba(0, 0, 0, 0.3);
22 top: 0;
23 left: 0;
24 z-index: 3;
25 backdrop-filter: blur(0.4rem);
26 pointer-events: initial;
27 transition: all 0.3s ease-in-out;
28}
29

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:

1const overlayVariants: Variants = {
2 hidden: { opacity: 0 },
3 visible: { opacity: 1 },
4 };
5

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.

1<AnimatePresence>
2 {open && (
3 <motion.div
4 className="drawerOverlay"
5 initial="hidden"
6 animate="visible"
7 exit="hidden"
8 onClick={handleCloseDrawer}
9 variants={overlayVariants}
10 />
11 )}
12</AnimatePresence>
13

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 komponentu
  • animate - jakie własności elementu mają zostać zanimowane - w naszym przypadku opacity ma zostać zanimowane od wartości 0 do wartości 1
  • exit - jaka animacja ma zostać odpalona na usunięcie elementu z DOM. W naszym przypadku będzie to animacja ze stanu visible do hidden

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:

1const variants: Variants = {
2 inactive: { y: 0 },
3 active: { y: '-80%' },
4 };
5

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:

1const handleDragEnd = (_: unknown, info: PanInfo): void => {
2 if (info.offset.y < 0 && info.offset.y < -80) {
3 setOpen(true);
4 }
5
6 if (info.offset.y > 0 && info.offset.y > 80) {
7 setOpen(false);
8 }
9};
10

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:

1<motion.div
2 className={'drawer'}
3 dragElastic={0.8}
4 onDragEnd={handleDragEnd}
5 dragConstraints={{ top: 0, bottom: 0 }}
6 drag="y"
7>
8 <motion.div
9 className={'drawerInner'}
10 transition={{ type: 'spring', duration: 0.8 }}
11 variants={variants}
12 initial="inactive"
13 animate={open ? 'active' : 'inactive'}
14 >
15 <DrawerHeader />
16 <div className='drawer-content'>
17 {/* Placeholder for content */}
18 </div>
19 </motion.div>
20</motion.div>
21

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 i transition - 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:

FramerMotionAnimationDrawer

Cały projekt dostępny jest na githubie: https://github.com/dawidkostrzewa/drawer-framer-motion

Zobacz więcej wpisów