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:
- 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:
1function Drawer() {2 const [open, setOpen] = useState(false);34 return (5 <>6 {open && (7 <div8 className="drawerOverlay"9 />10 )}11 <div12 className='drawer'13 >14 <div15 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}1213.drawerInner {14 height: 100%;15}1617.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.div4 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 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 stanuvisibledohidden
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 }56 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.div2 className={'drawer'}3 dragElastic={0.8}4 onDragEnd={handleDragEnd}5 dragConstraints={{ top: 0, bottom: 0 }}6 drag="y"7>8 <motion.div9 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ędragElasticitransition- 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

