Vă prezentăm Framer Motion
Publicat: 2022-03-10În acest articol, vom arunca o privire mai atentă asupra modului în care Framer Motion ne ajută să creăm animații extraordinare. Vom afla cum funcționează componentele de mișcare și vom învăța cum să înlănțuim animațiile. Vom analiza cum să facem animații declanșate prin gesturi, cronometrate și derulate cu mișcare Framer. Pe parcurs, vom folosi lucrurile pe care le învățăm pentru a construi cinci aplicații demonstrative pe care le-am configurat pentru a ne arăta cum putem integra Framer Motion în aplicațiile din lumea reală.
Acest tutorial va fi benefic pentru cititorii care sunt interesați să integreze animații în aplicația lor React.
Notă: Acest articol necesită o înțelegere de bază a React și CSS.
Ce este Framer Motion?
Framer Motion este o bibliotecă de animații care facilitează crearea de animații. API-ul său simplificat ne ajută să abstragem complexitățile din spatele animațiilor și ne permite să creăm animații cu ușurință.
Componente de mișcare
Acestea sunt elementele de bază ale mișcării Framer. Componentele de mișcare sunt create prin prefixarea motion
la elementul HTML și SVG obișnuit (de exemplu, motion.h1
). Componentele de mișcare pot accepta mai multe elemente de recuzită, cea de bază fiind recuzita animate
. Această reclamă preia un obiect în care definim proprietățile acelei componente pe care vrem să o animam. Proprietățile pe care le definim vor fi animate atunci când componenta se montează în DOM.
Să animem un text h1 folosind Framer Motion. Mai întâi, instalăm biblioteca framer-motion și importăm motion
.
npm i framer-motion import { motion } from 'framer-motion';
Apoi convertim h1 într-o componentă de mișcare.
<motion.h1 animate={{x: 20, y: -20}}> This is a motion component </motion.h1>
Acest lucru va face ca h1
să alunece 20px spre dreapta și să se miște cu 20px în sus când se încarcă. Când unitățile nu sunt adăugate, calculele se fac folosind pixeli. Cu toate acestea, puteți seta în mod explicit unitățile pe care doriți să se bazeze calculele, animate={{x: "20rem", y: "-20rem"}}>
.
În mod implicit, o componentă de mișcare va fi animată de la starea definită din stilurile sale la cele din animate
. Cu toate acestea, dacă am dori, am putea deturna și defini starea inițială de animație a componentei folosind suportul initial
. În timp ce suportul animate
este folosit pentru a defini comportamentul componentelor atunci când se montează, propul initial
definește comportamentul lor înainte de montare.
Dacă vrem ca h1-ul nostru să vină din stânga, îl controlăm folosind suportul inițial.
<motion.h1 initial={{x: -1000}} animate={{x: 20}}> This is a motion component </motion.h1>
Acum, când h1
se montează, alunecă din stânga.
Nu ne limităm la o singură animație. Putem defini o serie de animații numite keyframes
într-o matrice de valori. Fiecare valoare va fi animată în succesiune.
<motion.h1 initial={{x: -1000}} animate={{x: [20, 50, 0, -70, 40] }}> This is a motion component </motion.h1>
Elementul transition
ne permite să definim modul în care apar animațiile. Cu acesta, definim modul în care valorile se anima de la o stare la alta. Printre altele, putem defini duration
, delay
și type
de animație folosind această prop.
<motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion component </motion.h1>
Să presupunem că ar trebui să animam mai multe componente de mișcare simultan, ca în fragmentul de cod de mai jos.
<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>
În timp ce acest lucru funcționează, propul variants
din Framer Motion ne permite să extragem definițiile animației noastre într-un obiect de variante. Nu numai că variants
ne fac codul mai curat, dar ne permit să creăm animații și mai puternice și mai complexe.
Extragând definițiile noastre de animație în obiecte variante, avem asta:
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" } }
În loc să trecem direct definițiile de animație în elementele de recuzită initial
și animate
ale unei componente, extragem aceste definiții în obiecte variante independente. În obiectele variante, definim nume de variante care descriu numele fiecărei animații ca variante.
<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>
În variants
prop, trecem în numele obiectelor variante pentru fiecare componentă de mișcare și apoi trecem în animații la recuzita initial
și animate
.
Putem duce configurația actuală cu variante mai departe pentru a reduce repetarea. Folosind variante, putem propaga atributele de animație în jos prin DOM dintr-o componentă de mișcare părinte. Pentru ca acest lucru să funcționeze, creăm variante pentru motion.div
părinte cu nume de animație similare în obiectul său variant ca și copii. Făcând acest lucru, nu va trebui să transmitem numele animației fiecărei componente secundare. În culise, elementul părinte se ocupă de asta pentru noi.
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>
Acum avem un cod mai curat, fără repetări. Am transformat div-ul containerului într-o componentă de mișcare, astfel încât să putem trece în obiectul ContainerVariants
pe care l-am definit. Deoarece nu definim nicio animație pe container, trecem obiecte goale la initial
și animate
. Numele animației tale trebuie să fie aceleași în fiecare obiect variantă pentru ca propagarea să funcționeze.
Acum înțelegem elementele de bază ale Framer Motion. Să începem să construim primele 5 aplicații demonstrative.
Magazin de icoane
Putem crea animații interactive bazate pe gesturi. Componentele de mișcare sunt în prezent capabile să asculte pentru detectarea gesturilor de trecere, atingere, deplasare și tragere. Vom construi această aplicație Icon Shop folosind suportul whileHover
.
Componente
-
App.js
: acesta deține textele titlurilor. -
Card.jsx
: aici definim animațiile pentru cardurile cu pictograme. -
CardContainer.jsx
: importăm și parcurgem pictogramele. -
styles.js
: creați, stilați și exportați componentele de mișcare. Am folosit styled-components pentru stilizarea componentelor.
Să începem cu 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> );
Importăm componentele de mișcare H1
și H2
pe care le-am creat în fișierul Styles.js
. Deoarece sunt componente de mișcare, folosim recuzita initial
și animate
pentru a le defini comportamentul înainte și când se montează. Aici, importăm și afișăm și componenta CardContiner
.
Acum, 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> ); };
Aici, importăm SVG-urile, componenta de mișcare Container
și componenta Card
.
Similar cu H1
și H2
în App.js
, definim animații ale Container
folosind elementele de recuzită initial
și animate
. Când se încarcă, va crea un efect cool de alunecare din stânga browserului.
Acum, 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> ); };
Aici, creăm două obiecte variante cu animații beforeHover
și onHover
. În obiectul CardVariants
, nu vrem să facem nimic inițial, așa că beforeHover
este un obiect gol. onHover
creștem scara cutiei de carduri.
În obiectul IconVariants
, definim starea inițială a IconBox
-ului în beforeHover
. Îi setăm opacitatea la 0 și o împingem în sus cu 50px. Apoi, în onHover
, setăm opacitatea înapoi la 1, o împingem înapoi la poziția implicită și schimbăm tipul de tranziție la tween
. Apoi trecem în variante la componentele lor de mișcare respective. Folosim propagarea, așa că nu trebuie să setăm în mod explicit elementele de recuzită initial
și animate
la componenta IconBox
.
Bara de navigare animată
Vom construi o componentă simplă de navigare și vom vedea cum putem crea relații de sincronizare între componentele de mișcare părinte și copii.
Componente
-
App.js
: acesta deține textele titlurilor. -
Styles.js
: creați, stilați și exportați componentele de mișcare. Componentele sunt stilizate folosind componente-stilizate.
Să aruncăm o privire la fișierul 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 } };
Creăm o stare isOpen
care va fi folosită pentru a verifica dacă Navbar este deschisă sau nu. Creăm 3 obiecte variante, iconVariants
, menuVariants
și linkVariants
unde definim animațiile pentru componentele SvgBox
, Nav
și, respectiv, Link
motion. iconVariants
este folosită pentru a roti SvgBox
-ul cu 135 de grade atunci când trece cu mouse-ul deasupra. Nu trebuie să adăugăm „grad” la valoare. În meniul menuVariants
, controlăm poziția de sus a Nav
așa cum ați folosi proprietatea de position
în CSS. Comutăm în poziția de sus a Nav
pe baza stării isOpen
.
Cu variante, putem crea relații de sincronizare între componentele de mișcare ale părinților și ale copiilor. Definim relația dintre Nav
părinte și copilul său, Link
folosind proprietatea when
din obiectul de tranziție. Aici, setați-l la beforeChildren
, astfel încât animațiile componentei părinte să se termine înainte ca animația copilului să înceapă.
Folosind proprietatea staggerChildren
, setăm o ordine de sincronizare pentru fiecare link. Fiecare link va dura 0,5 secunde pentru a apărea unul după altul. Acest lucru creează un indiciu vizual frumos atunci când Nav
este deschis. În linkVariants
animăm opacitatea și poziția verticală a fiecărei legături.
<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>
Aici, trecem în variante la componentele lor respective. În SvgBox
, comutăm starea isOpen
ori de câte ori se face clic pe acesta, apoi îl animăm condiționat în funcție de stare. La fel ca SvgBox
, animam condiționat Nav
și Link
-urile pe baza isOpen
.
Modal animat
Vom construi o componentă modală și vom afla despre AnimatePresence
de la Framer Motion și cum ne permite să animam elemente pe măsură ce părăsesc DOM.
Componente:
-
App.js
: am configurat stareashowModal
aici. -
Modal.jsx
: activitatea de animație reală are loc aici. -
Styles.js
: creați, stilați și exportați componentele de mișcare. Componentele sunt stilizate folosind componente-stilizate.
Să ne uităm la 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> ); }
Creăm o stare showModal
care va fi folosită pentru a reda condiționat modalul. Funcția toggleModal
va comuta starea ori de câte ori se face clic pe ToggleButton
. ToggleButton
este o componentă de mișcare, așa că putem defini animații pentru aceasta. Când se montează, alunecă din stânga. Această animație rulează timp de 0,5 secunde. De asemenea, trecem în starea showModal
la Modal
prin recuzită.
Acum, 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>
Importăm AnimatePresence
din framer-motion
. Ne permite să setăm animații de ieșire pentru componente atunci când părăsesc DOM. Modal
în mod condiționat pe baza stării showModal
. Definim animațiile pentru ModalBox
și ModalContent
prin elementele de recuzită initial
și animate
. Există și o nouă recuzită aici, exit
. Având AnimatePresence
ca wrapper, ne permite să adăugăm animații de ieșire la ModalBox
în suportul de exit
.
Defilare animație
Vom folosi o combinație a cârligului useAnimation
și react-intersection-observer
pentru a crea animații declanșate de defilare.
Componente
-
App.js
: am configurat animațiile pentru componentaBox
și o redăm înApp
-
Styles.js
: creați, stilați și exportați componentele de mișcare. Componentele sunt stilizate folosind componente-stilizate.
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} /> ); };
Cârligul useAnimation
ne permite să controlăm secvențele în care apar animațiile noastre. Avem acces la metodele controls.start
și controls.stop
pe care le putem folosi pentru a porni și opri manual animațiile noastre. Trecem animația hidden
inițială către StyledBox
. Trecem controalele pe care le-am definit cu metoda start
la StyledBox
animate prop.
Cârligul useInView
al lui react-intersection-observer
ne permite să urmărim când o componentă este vizibilă în fereastra de vizualizare. Cârligul useInView
ne oferă acces la ref
, pe care îl trecem la componenta pe care vrem să o urmărim, și la inView
, care ne spune dacă acel element este sau nu inView
. Folosim useEffect
pentru a apela controls.start
ori de câte ori elementul pe care îl urmărim, StyledBox
este la vedere. Transmitem controls
și inView
ca dependențe ale lui useEffect
. De asemenea, trecem în variantele pe care le-am definit, BoxVariants
la StyledBox
.
Animație erou
Vom construi o animație banner de erou cool folosind cârligul useCycle
. Vom înțelege cum useCycle
ne permite să parcurgem animații.
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" }, }, };
Definim 3 variante, H1Variants
, TextVariants
și BannerVariants
. Cu toate acestea, ne concentrăm pe BannerVariants
. Definim 2 animații, animationOne
și animationTwo
în BannerVariants
. Acestea sunt animațiile pe care le trecem în useCycle
pentru a parcurge.
const [animation, cycleAnimation] = useCycle("animationOne", "animationTwo"); useEffect(() => { setTimeout(() => { cycleAnimation(); }, 2000); }, []);
useCycle
funcționează similar cu cârligul useState
. În matricea destructurată, animation
reprezintă animația care este activă, fie animationOne
sau animationTwo
. Funcția cylceAnimation
care circulă între animația pe care am definit-o. Trecem animațiile prin care vrem să le parcurgem în useCycle
și apelăm cylceAnimation
după 2 secunde în 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>
La sfârșitul tuturor, trecem variantele la componentele lor respective și urmărim cum se întâmplă magia. Cu aceasta, Banner
-ul va aluneca inițial din dreapta pe baza animațiilor pe care le-am definit în animationOne
, iar după 2 secunde va fi apelat cycleAnimation
care va declanșa animationTwo
.
Așa cum a spus odată un porc înțelept, „asta sunt tot oameni buni”.
Concluzie
Am trecut prin elementele de bază ale Framer Motion și am văzut câteva proiecte demonstrative care ne oferă o privire asupra gamei de animații pe care le putem crea. Cu toate acestea, puteți face mult mai mult cu el. Vă încurajez să vă scufundați în documente și să vă dezlănțuiți.
Resurse
- Framer Motion Api Docs, Framer Motion
- reacție-intersecție-observator, npm
- Framer Motion pentru React, NetNinja