Przedstawiamy Ruch Framer

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Poprawnie wykonane animacje mają ogromne możliwości. Jednak tworzenie przyciągających wzrok animacji za pomocą CSS może być trudne. Wchodzi Framer Motion. Dzięki Framer Motion nie musisz być ekspertem od CSS, aby tworzyć piękne animacje. Framer Motion zapewnia nam animacje gotowe do produkcji oraz interfejs API niskiego poziomu, z którym możemy wchodzić w interakcje, aby zintegrować te animacje z naszymi aplikacjami.

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

Więcej po skoku! Kontynuuj czytanie poniżej ↓

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 stan showModal .
  • 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 komponentu Box i renderujemy je w App
  • 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