Przedstawiamy Ruch Framer
Opublikowany: 2022-03-10W tym artykule przyjrzymy się, jak Framer Motion pomaga nam w tworzeniu niesamowitych animacji. Dowiemy się, jak działają komponenty ruchu i nauczymy się łączyć ze sobą animacje. Przyjrzymy się, jak tworzyć animacje uruchamiane gestami, synchronizowane w czasie i przewijane za pomocą ruchu Framer. Po drodze wykorzystamy to, czego się nauczyliśmy, do zbudowania pięciu aplikacji demonstracyjnych, które skonfigurowałem, aby pokazać nam, jak możemy zintegrować Framer Motion z aplikacjami w świecie rzeczywistym.
Ten samouczek przyda się czytelnikom zainteresowanym integracją animacji w swojej aplikacji React.
Uwaga: ten artykuł wymaga podstawowej wiedzy na temat React i CSS.
Co to jest ruch framer?
Framer Motion to biblioteka animacji, która ułatwia tworzenie animacji. Jego uproszczone API pomaga nam wyabstrahować złożoność animacji i pozwala nam z łatwością tworzyć animacje.
Komponenty ruchu
To są elementy składowe ruchu Framer. Komponenty ruchu są tworzone przez dodanie motion
do zwykłego elementu HTML i SVG (np. motion.h1
). Komponenty ruchu mogą przyjmować kilka rekwizytów, z których podstawową jest rekwizyt animate
. Ten rekwizyt przyjmuje obiekt, w którym definiujemy właściwości tego komponentu, który chcemy animować. Zdefiniowane przez nas właściwości będą animowane, gdy komponent zostanie zamontowany w DOM.
Animujmy tekst h1 za pomocą Framer Motion. Najpierw instalujemy bibliotekę framer-motion i importujemy motion
.
npm i framer-motion import { motion } from 'framer-motion';
Następnie przekształcamy h1 w składową ruchu.
<motion.h1 animate={{x: 20, y: -20}}> This is a motion component </motion.h1>
Spowoduje to, że h1
przesunie się o 20 pikseli w prawo i przesunie się o 20 pikseli w górę podczas ładowania. Gdy jednostki nie są dodawane, obliczenia są wykonywane przy użyciu pikseli. Możesz jednak jawnie ustawić jednostki, na których chcesz opierać obliczenia, animate={{x: "20rem", y: "-20rem"}}>
.
Domyślnie komponent ruchu będzie animowany od stanu zdefiniowanego w jego stylach do stanu w animate
rekwizycie. Jednak gdybyśmy chcieli, moglibyśmy przejąć kontrolę i zdefiniować początkowy stan animacji komponentu za pomocą initial
właściwości. Podczas gdy właściwość animate
służy do definiowania zachowania komponentów podczas ich montowania, initial
właściwość określa ich zachowanie przed ich zamontowaniem.
Jeśli chcemy, aby nasze h1 pojawiło się z lewej strony, kontrolujemy to za pomocą początkowego parametru.
<motion.h1 initial={{x: -1000}} animate={{x: 20}}> This is a motion component </motion.h1>
Teraz, kiedy h1
się montuje, wsuwa się z lewej strony.
Nie ograniczamy się do jednej animacji. Możemy zdefiniować serię animacji zwanych keyframes
w tablicy wartości. Każda wartość będzie animowana po kolei.
<motion.h1 initial={{x: -1000}} animate={{x: [20, 50, 0, -70, 40] }}> This is a motion component </motion.h1>
Rekwizyt transition
pozwala nam zdefiniować, w jaki sposób mają się pojawiać animacje. Dzięki niemu definiujemy, w jaki sposób wartości animują się z jednego stanu do drugiego. Za pomocą tego rekwizytu możemy między innymi zdefiniować duration
, delay
i type
animacji.
<motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion component </motion.h1>
Powiedzmy, że mamy animować kilka komponentów ruchu jednocześnie, jak w poniższym fragmencie kodu.
<div className="App"> <motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion h1 </motion.h1> <motion.h2 initial={{ y: -1000 }} animate={{ y: 0 }} transition={{ type: "tween", duration: "1", delay: ".4" }}>This is a motion h2 </motion.h2> <motion.h3 initial={{ x: 100, opacity: 0 }} animate={{ x: 0, opacity: 1 }}> This is a motion h3 </motion.h3> <motion.h4 initial={{ scale: 0.7 }} animate={{ scale: 1.7 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion h4 </motion.h4> </div>
Chociaż to działa, podpórka variants
w programie Framer Motion umożliwia nam wyodrębnienie definicji animacji do obiektu wariantów. variants
nie tylko sprawiają, że nasz kod jest czystszy, ale pozwalają nam tworzyć jeszcze potężniejsze i bardziej złożone animacje.
Wyodrębniając nasze definicje animacji do obiektów wariantów, mamy to:
const H1Variants = { initial: { x: -1000 }, animate: { x: 0 }, transition: { type: "tween", duration: 2, delay: 1 } } const H2Variants = { initial: { y: -1000 }, animate: { y: 0 }, transition: { type: "tween", duration: 1, delay: .4 } } const H3Variants = { initial:{ x: 100, opacity: 0 }, animate:{ x: 0, opacity: 1 } } const H4Variants = { initial:{ scale: 0.7 }, animate:{ scale: 1.7 }, transition:{ type: "tween", duration: "2", delay: "1" } }
Zamiast przekazywać definicje animacji bezpośrednio do właściwości initial
i animate
komponentu, wyodrębniamy te definicje do samodzielnych obiektów wariantowych. W obiektach wariantów definiujemy nazwy wariantów, które opisują nazwę każdej animacji jako warianty.
<div className="App"> <motion.h1 variants={H1Variants} initial='initial' animate='animate' > This is a motion h1 </motion.h1> <motion.h2 variants={H2Variants} initial='initial' animate='animate' > This is a motion h2 </motion.h2> <motion.h3 variants={H3Variants} initial='initial' animate='animate' > This is a motion h3 </motion.h3> <motion.h4 variants={H4Variants} initial='initial' animate='animate' > This is a motion h4 </motion.h4> </div>
W variants
rekwizytu przekazujemy nazwę wariantu obiektów dla każdego komponentu ruchu, a następnie przekazujemy animacje do rekwizytów initial
i animate
.
Możemy posunąć naszą obecną konfigurację z wariantami dalej, aby zmniejszyć liczbę powtórzeń. Używając wariantów, możemy propagować atrybuty animacji w dół przez DOM z nadrzędnego komponentu ruchu. Aby to zadziałało, tworzymy warianty dla rodzica motion.div
z podobnymi nazwami animacji w jego obiekcie wariantu, jak jego dzieci. Dzięki temu nie będziemy musieli przekazywać nazw animacji do każdego komponentu podrzędnego. Za kulisami zajmuje się tym za nas element nadrzędny.
const ContainerVariants = { initial: {}, animate: {} }; const H1Variants = { initial: { x: -1000 }, animate: { x: 0 }, transition: { type: "tween", duration: 2, delay: 1 } }; //more variants below <motion.div className="App" variants={ContainerVariants} initial="initial" animate="animate" > <motion.h1 variants={H1Variants}>This is a motion h1</motion.h1> <motion.h2 variants={H2Variants}>This is a motion h2</motion.h2> <motion.h3 variants={H3Variants}>This is a motion h3</motion.h3> <motion.h4 variants={H4Variants}>This is a motion h4</motion.h4> </motion.div>
Teraz mamy czystszy kod bez powtórzeń. Przekształciliśmy element div kontenera w komponent ruchu, abyśmy mogli przekazać zdefiniowany przez nas obiekt ContainerVariants
. Ponieważ nie definiujemy żadnych animacji w kontenerze, przekazujemy puste obiekty do initial
i animate
. Aby propagacja działała, nazwy animacji muszą być takie same w każdym obiekcie wariantowym.
Teraz rozumiemy podstawy Framer Motion. Zacznijmy budować naszą pierwszą z 5 aplikacji demonstracyjnych.
Sklep z ikonami
Potrafimy tworzyć interaktywne animacje oparte na gestach. Komponenty ruchu mogą obecnie nasłuchiwać wykrywania gestów najechania, dotknięcia, panoramowania i przeciągania. Będziemy budować tę aplikację Icon Shop za pomocą rekwizytu whileHover
.
składniki
-
App.js
: zawiera teksty nagłówków. -
Card.jsx
: tutaj definiujemy animacje kart ikon. -
CardContainer.jsx
: importujemy i przechodzimy przez ikony. -
styles.js
: twórz, stylizuj i eksportuj komponenty ruchu. Użyłem styled-components do stylizacji komponentów.
Zacznijmy od App.js
.
import { H1, H2 } from "./Styles"; import CardContainer from "./CardContainer"; return ( <div> <H1 initial={{ y: -100 }} animate={{ y: 0, transition: { delay: 1 } }}> Icon Shop </H1> <H2 initial={{ x: -1000 }} animate={{ x: 0, transition: { delay: 1 } }}> Hover over the cards to see the motion magic </H2> <CardContainer /> </div> );
Importujemy komponenty ruchu H1
i H2
, które stworzyliśmy w pliku Styles.js
. Ponieważ są one komponentami ruchu, używamy rekwizytów initial
i animate
, aby zdefiniować ich zachowanie przed i po zamontowaniu. Tutaj również importujemy i wyświetlamy komponent CardContiner
.
Teraz CardContainer.js
.
import { Container } from "./Styles"; import Card from "./Card"; import { ReactComponent as AddIcon } from "./assets/add.svg"; import { ReactComponent as AirplaneIcon } from "./assets/airplane.svg"; import { ReactComponent as AlarmIcon } from "./assets/alarm.svg"; //more svg imports below... const icons = [ <AddIcon />, <AirplaneIcon />, <AlarmIcon />, //more icons below ]; const CardContainer = () => { return ( <Container initial={{ x: -1000 }} animate={{ x: 0 }}> {icons.map((icon) => ( <Card icon={icon} /> ))} </Container> ); };
Tutaj importujemy pliki SVG, komponent ruchu Container
i komponent Card
.
Podobnie jak w przypadku H1
i H2
w App.js
, definiujemy animacje Container
za pomocą initial
i animate
rekwizytów. Po załadowaniu stworzy fajny efekt przesuwania się z lewej strony przeglądarki.
Teraz Card.js
import { CardBox, IconBox } from "./Styles"; const CardVariants = { beforeHover: {}, onHover: { scale: 1.1 } }; const IconVariants = { beforeHover: { opacity: 0, y: -50 }, onHover: { opacity: 1, y: 0, scale: 1.5, transition: { type: "tween" } } }; const Card = ({ icon }) => { console.log(icon); return ( <CardBox variants={CardVariants} initial="beforeHover" whileHover="onHover"> <IconBox variants={IconVariants}>{icon}</IconBox> </CardBox> ); };
Tutaj tworzymy dwa warianty obiektów z animacjami beforeHover
i onHover
. W obiekcie CardVariants
początkowo nie chcemy nic robić, więc beforeHover
jest pustym obiektem. onHover
zwiększamy skalę pudełka na karty.
W obiekcie IconVariants
definiujemy stan początkowy IconBox
w jego beforeHover
. Ustawiamy jego krycie na 0 i przesuwamy go w górę o 50px. Następnie w onHover
ustawiamy krycie z powrotem na 1, przesuwamy je z powrotem do domyślnej pozycji i zmieniamy typ przejścia na tween
. Następnie przekazujemy warianty do ich odpowiednich składowych ruchu. Korzystamy z propagacji, więc nie musimy jawnie ustawiać initial
i animate
właściwości komponentu IconBox
.
Animowany pasek nawigacyjny
Zbudujemy prosty komponent Nawigacja i zobaczymy, jak możemy stworzyć relacje czasowe między komponentami ruchu rodzica i dziecka.
składniki
-
App.js
: zawiera teksty nagłówków. -
Styles.js
: twórz, stylizuj i eksportuj komponenty ruchu. Komponenty są stylizowane przy użyciu styled-components.
Przyjrzyjmy się plikowi App.js
import { Header, Nav, Link, SvgBox } from "./Styles"; function App() { const [isOpen, setIsOpen] = useState(false); const iconVariants = { opened: { rotate: 135 }, closed: { rotate: 0 } }; const menuVariants = { opened: { top: 0, transition: { when: "beforeChildren", staggerChildren: 0.5 } }, closed: { top: "-90vh" } }; const linkVariants = { opened: { opacity: 1, y: 50 }, closed: { opacity: 0, y: 0 } };
Tworzymy stan isOpen
, który będzie używany do sprawdzenia, czy pasek nawigacyjny jest otwarty, czy nie. Tworzymy 3 warianty obiektów, iconVariants
, menuVariants
i linkVariants
, w których definiujemy animacje odpowiednio dla komponentów ruchu SvgBox
, Nav
i Link
. iconVariants
służy do obracania SvgBox
o 135 stopni po najechaniu na niego. Nie musimy dodawać „stopni” do wartości. W menuVariants
kontrolujemy górną pozycję Nav
, tak jakbyś używał właściwości position
w CSS. Przełączamy górną pozycję Nav
na podstawie stanu isOpen
.
Dzięki wariantom możemy tworzyć relacje czasowe między komponentami ruchu rodzica i dziecka. Relację między nadrzędnym Nav
a jego dzieckiem, Link
definiujemy za pomocą właściwości when
w obiekcie przejściowym. Tutaj ustaw go na beforeChildren
, aby animacje komponentu nadrzędnego zakończyły się przed rozpoczęciem animacji dziecka.
Korzystając z właściwości staggerChildren
, ustalamy kolejność czasową dla każdego łącza. Każde łącze zajmie 0,5 sekundy, aby pojawić się jeden po drugim. Stwarza to miłą wizualną wskazówkę, gdy Nav
jest otwarty. W linkVariants
przezroczystość i pionową pozycję każdego łącza.
<div className="App"> <Header> <SvgBox variants={iconVariants} animate={isOpen ? "opened" : "closed"} onClick={() => setIsOpen(!isOpen)} > <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="https://www.w3.org/2000/svg" > <path d="M12 4C11.4477 4 11 4.44772 11 5V11H5C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13H11V19C11 19.5523 11.4477 20 12 20C12.5523 20 13 19.5523 13 19V13H19C19.5523 13 20 12.5523 20 12C20 11.4477 19.5523 11 19 11H13V5C13 4.44772 12.5523 4 12 4Z" fill="#fff" /> </svg> </SvgBox> </Header> <Nav initial={false} variants={menuVariants} animate={isOpen ? "opened" : "closed"} > <Link variants={linkVariants}>home</Link> <Link variants={linkVariants}>about</Link> <Link variants={linkVariants}>gallery</Link> </Nav> </div>
Tutaj przechodzimy w wariantach do ich odpowiednich komponentów. W SvgBox
przełączamy stan isOpen
każdym kliknięciu, a następnie warunkowo animujemy go w oparciu o stan. Podobnie jak SvgBox
, warunkowo animujemy Nav
i Link
w oparciu o isOpen
.
Animowany modalny
Zbudujemy komponent modalny i dowiemy się o AnimatePresence
w Framer Motion oraz o tym, jak pozwala nam animować elementy opuszczające DOM.
Składniki:
-
App.js
: tutaj ustawiamy stanshowModal
. -
Modal.jsx
: tutaj odbywa się właściwa praca nad animacją. -
Styles.js
: twórz, stylizuj i eksportuj komponenty ruchu. Komponenty są stylizowane przy użyciu styled-components.
Przyjrzyjmy się App.js
import { ToggleButton, Container } from "./Styles"; import Modal from "./Modal"; function App() { const [showModal, setShowModal] = useState(false); const toggleModal = () => { setShowModal(!showModal); }; return ( <Container> <ToggleButton initial={{ x: -700 }} animate={{ x: 0, transition: { duration: 0.5 } }} onClick={toggleModal} > Toggle Modal </ToggleButton> <Modal showModal={showModal} /> </Container> ); }
Tworzymy stan showModal
, który będzie używany do warunkowego renderowania modalnego. Funkcja toggleModal
będzie przełączać stan za każdym razem, gdy klikniesz ToggleButton
. ToggleButton
to komponent ruchu, więc możemy zdefiniować dla niego animacje. Po zamontowaniu wsuwa się z lewej strony. Ta animacja trwa 0,5 sekundy. Przekazujemy również stan showModal
do Modal
przez rekwizyty.
Teraz Modal.jsx
import { AnimatePresence } from "framer-motion"; import { ModalBox, ModalContent, Container } from "./Styles"; <Container> <AnimatePresence> {showModal && ( <ModalBox initial={{ opacity: 0, y: 60, scale: 0.3 }} animate={{ opacity: 1, y: 0, scale: 1, transition: { type: "spring", stiffness: 300 } }} exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.6 } }} > <ModalContent initial={{ y: -30, opacity: 0 }} animate={{ y: 0, opacity: 1, transition: { delay: 1 } }} > Modal content!!!! </ModalContent> </ModalBox> )} </AnimatePresence> </Container>
AnimatePresence
importujemy z framer framer-motion
. Pozwala nam ustawić animacje wyjścia dla komponentów, gdy opuszczają DOM. Warunkowo renderujemy Modal
na podstawie stanu showModal
. Definiujemy animacje dla ModalBox
i ModalContent
poprzez ich initial
i animate
rekwizyty. Jest tu też nowy rekwizyt, exit
. Posiadanie AnimatePresence
jako opakowania pozwala nam dodawać animacje wyjścia do ModalBox
we właściwościach exit
.
Przewiń animację
Użyjemy kombinacji haka useAnimation
i react-intersection-observer
aby stworzyć animacje wyzwalane przewijaniem.
składniki
-
App.js
: ustawiamy animacje dla komponentuBox
i renderujemy je wApp
-
Styles.js
: twórz, stylizuj i eksportuj komponenty ruchu. Komponenty są stylizowane przy użyciu styled-components.
import React, { useEffect } from "react"; import { useAnimation } from "framer-motion"; import { useInView } from "react-intersection-observer"; import { Container, H1,StyledBox } from "./Styles"; const BoxVariants = { visible: { opacity: 1, x: 0, transition: { duration: 1 } }, hidden: { opacity: 0, x: 300 }, }; const Box = () => { const controls = useAnimation(); const [ref, inView] = useInView(); useEffect(() => { if (inView) { controls.start("visible"); } }, [controls, inView]); return ( <StyledBox ref={ref} animate={controls} initial="hidden" variants={BoxVariants} /> ); };
Hak useAnimation
pozwala nam kontrolować sekwencje, w których występują nasze animacje. Mamy dostęp do metod controls.stop
controls.start
których możemy użyć do ręcznego uruchamiania i zatrzymywania naszych animacji. Przekazujemy inicjalną hidden
animację do StyledBox
. Przekazujemy kontrolki, które zdefiniowaliśmy za pomocą metody start
, do StyledBox
animowanej prop.
Hak useInView
w React react-intersection-observer
pozwala nam śledzić, kiedy komponent jest widoczny w rzutni. Hak useInView
daje nam dostęp do ref
, który przekazujemy do komponentu, który chcemy obejrzeć, oraz do wartości logicznej inView
, która mówi nam, czy dany element jest inView
, czy nie. Używamy useEffect
do wywołania control.start za każdym razem controls.start
gdy oglądany element, StyledBox
jest w widoku. Przekazujemy controls
i inView
jako zależności useEffect
. Ponadto przekazujemy zdefiniowane przez nas warianty, BoxVariants
do StyledBox
.
Animacja bohatera
Zbudujemy fajną animację baneru bohatera za pomocą haka useCycle
. Zrozumiemy, w jaki sposób useCycle
pozwala nam przechodzić przez animacje.
import React, { useEffect } from "react"; import { useCycle } from "framer-motion"; import { Container, H1, HeroSection, Banner, TextBox } from "./Styles"; import { ReactComponent as BannerIllustration } from "./bighead.svg"; const H1Variants = { initial: { y: -200, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 1 } }, }; const TextVariants = { initial: { x: 400 }, animate: { x: 0, transition: { duration: 0.5 } }, }; const BannerVariants = { animationOne: { x: -250, opacity: 1, transition: { duration: 0.5 } }, animationTwo: { y: [0, -20], opacity: 1, transition: { yoyo: Infinity, ease: "easeIn" }, }, };
Definiujemy 3 warianty, H1Variants
, TextVariants
i BannerVariants
. Jednak naszym celem jest BannerVariants
. W BannerVariants
definiujemy 2 animacje, animationOne
i animationTwo
. Są to animacje, które przekazujemy do useCycle
, aby przejść przez.
const [animation, cycleAnimation] = useCycle("animationOne", "animationTwo"); useEffect(() => { setTimeout(() => { cycleAnimation(); }, 2000); }, []);
useCycle
działa podobnie do haka useState
. W zdestrukturyzowanej tablicy animation
reprezentuje aktywną animację, niezależnie od tego, czy animationOne
, czy animationTwo
. Funkcja cylceAnimation
, która przełącza się między zdefiniowaną przez nas animacją. Przekazujemy animacje, które chcemy przejść do useCycle
i wywołujemy cylceAnimation
po 2 sekundach w useEffect
.
<div className="App"> <Container> <H1 variants={H1Variants} initial="initial" animate="animate"> Cool Hero Section Anmiation </H1> <HeroSection> <TextBox variants={TextVariants} initial="initial" animate="animate"> Storage shed, troughs feed bale manure, is garden wheat oats at augers. Bulls at rose garden cucumbers mice sunflower wheat in pig. Chainsaw foal hay hook, herbs at combine harvester, children is mallet. Goat goose hen horse. Pick up truck livestock, pets and storage shed, troughs feed bale manure, is garden wheat oats at augers. Lamb. </TextBox> <Banner variants={BannerVariants} animate={animation}> <BannerIllustration /> </Banner> </HeroSection> </Container> </div>
Na koniec przekazujemy warianty do ich poszczególnych komponentów i obserwujemy, jak dzieje się magia. Dzięki temu Banner
początkowo przesunie się z prawej strony na podstawie animacji, które zdefiniowaliśmy w animationOne
, a po 2 sekundach zostanie cycleAnimation
, który uruchomi animationTwo
.
Jak powiedział kiedyś mądry Świnia: „to wszyscy ludzie”.
Wniosek
Przejrzeliśmy podstawy Framer Motion i zobaczyliśmy kilka projektów demonstracyjnych, które dają nam wgląd w zakres animacji, które możemy stworzyć. Jednak możesz z nim zrobić o wiele więcej. Zachęcam do zagłębienia się w dokumenty i szaleństwa.
Zasoby
- Dokumentacja ramek ruchu Api, ruch ramek
- obserwator przecięcia reakcji, npm
- Framer Motion dla Reacta, NetNinja