Vă prezentăm Framer Motion

Publicat: 2022-03-10
Rezumat rapid ↬ Animațiile, când sunt făcute corect, sunt puternice. Cu toate acestea, crearea de animații atrăgătoare cu CSS poate fi dificilă. Vine Framer Motion. Cu Framer Motion, nu trebuie să fii un expert CSS pentru a realiza animații frumoase. Framer Motion ne oferă animații pregătite pentru producție și un API de nivel scăzut cu care putem interacționa pentru a integra aceste animații în aplicațiile noastre.

Î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"}}> .

Mai multe după săritură! Continuați să citiți mai jos ↓

Î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 starea showModal 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 componenta Box și o redăm în App
  • 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