Apresentando o Framer Motion

Publicados: 2022-03-10
Resumo rápido ↬ Animações, quando bem feitas, são poderosas. No entanto, criar animações atraentes com CSS pode ser complicado. Entra o Framer Motion. Com o Framer Motion, você não precisa ser um especialista em CSS para fazer belas animações. O Framer Motion nos fornece animações prontas para produção e uma API de baixo nível com a qual podemos interagir para integrar essas animações em nossos aplicativos.

Neste artigo, veremos mais de perto como o Framer Motion nos ajuda a criar animações incríveis. Aprenderemos como os componentes de movimento funcionam e como encadear animações. Veremos como fazer animações acionadas por gestos, cronometradas e de rolagem com o movimento do Framer. Ao longo do caminho, usaremos o que aprendemos para criar cinco aplicativos de demonstração que configurei para nos mostrar como podemos integrar o Framer Motion em aplicativos do mundo real.

Este tutorial será benéfico para os leitores interessados ​​em integrar animações em seu aplicativo React.

Nota: Este artigo requer uma compreensão básica de React e CSS.

O que é o movimento do Framer?

Framer Motion é uma biblioteca de animação que facilita a criação de animações. Sua API simplificada nos ajuda a abstrair as complexidades por trás das animações e nos permite criar animações com facilidade.

Componentes de movimento

Estes são os blocos de construção do movimento do Framer. Os componentes de movimento são criados prefixando motion ao seu elemento HTML e SVG regular (por exemplo, motion.h1 ). Os componentes de movimento podem aceitar vários adereços, sendo o básico o adereço animate . Este prop recebe um objeto onde definimos as propriedades daquele componente que queremos animar. As propriedades que definimos serão animadas quando o componente for montado no DOM.

Vamos animar um texto h1 usando o Framer Motion. Primeiro, instalamos a biblioteca framer-motion e importamos motion .

 npm i framer-motion import { motion } from 'framer-motion';

Em seguida, convertemos o h1 em um componente de movimento.

 <motion.h1 animate={{x: 20, y: -20}}> This is a motion component </motion.h1>

Isso fará com que o h1 deslize 20px para a direita e mova 20px para cima quando carregar. Quando as unidades não são adicionadas, os cálculos são feitos usando pixels. No entanto, você pode definir explicitamente as unidades nas quais deseja que os cálculos sejam baseados, animate={{x: "20rem", y: "-20rem"}}> .

Mais depois do salto! Continue lendo abaixo ↓

Por padrão, um componente de movimento será animado a partir do estado definido de seus estilos para os da prop animate . No entanto, se quiséssemos, poderíamos sequestrar e definir o estado de animação inicial do componente usando o prop initial . Enquanto a prop animate é usada para definir o comportamento dos componentes quando eles são montados, a prop initial define seu comportamento antes da montagem.

Se quisermos que nosso h1 venha da esquerda, controlamos isso usando a prop inicial.

 <motion.h1 initial={{x: -1000}} animate={{x: 20}}> This is a motion component </motion.h1>

Agora, quando o h1 é montado, ele desliza da esquerda.

Não estamos limitados a uma única animação. Podemos definir uma série de animações chamadas keyframes em uma matriz de valores. Cada valor será animado em sequência.

 <motion.h1 initial={{x: -1000}} animate={{x: [20, 50, 0, -70, 40] }}> This is a motion component </motion.h1>

O prop de transition nos permite definir como as animações ocorrem. Com ele, definimos como os valores se animam de um estado para outro. Entre outras coisas, podemos definir a duration , o delay e o type de animação usando este prop.

 <motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion component </motion.h1>

Digamos que deveríamos animar vários componentes de movimento simultaneamente, como no trecho de código abaixo.

 <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>

Enquanto isso funciona, o prop de variants no Framer Motion nos permite extrair nossas definições de animação em um objeto de variantes. As variants não apenas tornam nosso código mais limpo, mas também nos permitem criar animações ainda mais poderosas e complexas.

Extraindo nossas definições de animação em objetos variantes, temos isso:

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

Em vez de passar as definições de animação diretamente para as props initial e animate de um componente, extraímos essas definições em objetos variantes independentes. Nos objetos variantes, definimos nomes de variantes que descrevem o nome de cada animação como variantes.

 <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>

Na prop variants , passamos o nome dos objetos variantes para cada componente de movimento e, em seguida, passamos as animações para as props initial e animate .

Podemos levar nossa configuração atual com variantes ainda mais para reduzir a repetição. Usando variantes, podemos propagar atributos de animação pelo DOM a partir de um componente de movimento pai. Para que isso funcione, criamos variantes para o motion.div pai com nomes de animação semelhantes em seu objeto variante como seus filhos. Ao fazer isso, não teremos que passar os nomes das animações para cada componente filho. Nos bastidores, o elemento pai cuida disso para nós.

 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>

Agora temos um código mais limpo sem repetições. Transformamos a div do contêiner em um componente de movimento para que pudéssemos passar o objeto ContainerVariants que definimos. Como não definimos nenhuma animação no contêiner, passamos objetos vazios para initial e animate . Seus nomes de animação devem ser os mesmos em cada objeto variante para que a propagação funcione.

Agora entendemos o básico do Framer Motion. Vamos começar a construir nosso punho de 5 aplicativos de demonstração.

Loja de ícones

Podemos criar animações interativas baseadas em gestos. Atualmente, os componentes de movimento são capazes de ouvir a detecção de gestos de passar o mouse, tocar, deslocar e arrastar. Estaremos construindo este aplicativo Icon Shop usando o suporte whileHover .

Componentes

  • App.js : contém os textos do cabeçalho.
  • Card.jsx : aqui, definimos as animações para os cartões de ícones.
  • CardContainer.jsx : importamos e percorremos os ícones.
  • styles.js : cria, estiliza e exporta os componentes de movimento. Eu usei styled-components para estilizar os componentes.

Vamos começar com 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> );

Importamos os componentes de movimento H1 e H2 que criamos no arquivo Styles.js . Como eles são componentes de movimento, usamos os adereços initial e animate para definir seu comportamento antes e quando eles são montados. Aqui, também importamos e exibimos o componente CardContiner .

Agora, o 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> ); };

Aqui, importamos os SVGs, o componente de movimento Container e o componente Card .

Semelhante a H1 e H2 em App.js , definimos animações do Container usando as props initial e animate . Quando carregar, criará um efeito legal de deslizar da esquerda do navegador.

Agora, 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> ); };

Aqui, criamos dois objetos variantes com animações beforeHover e onHover . No objeto CardVariants , não queremos fazer nada inicialmente, então beforeHover é um objeto vazio. onHover aumentamos a escala da caixa de cartão.

No objeto IconVariants , definimos o estado inicial do IconBox em seu beforeHover . Definimos sua opacidade como 0 e a empurramos para cima em 50px. Então, em onHover , definimos a opacidade de volta para 1, empurramos de volta para sua posição padrão e alteramos o tipo de transição para tween . Em seguida, passamos as variantes para seus respectivos componentes de movimento. Fazemos uso da propagação, então não precisamos definir explicitamente as props initial e animate para o componente IconBox .

Barra de navegação animada

Construiremos um componente de navegação simples e veremos como podemos criar relações de tempo entre os componentes de movimento pai e filho.

Componentes

  • App.js : contém os textos do cabeçalho.
  • Styles.js : crie, estilize e exporte os componentes de movimento. Os componentes são estilizados usando styled-components.

Vamos dar uma olhada no arquivo 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 } };

Criamos um estado isOpen que será usado para verificar se a Navbar está aberta ou não. Criamos 3 objetos variantes, iconVariants , menuVariants e linkVariants , onde definimos as animações para os componentes de movimento SvgBox , Nav e Link respectivamente. O iconVariants é usado para girar o SvgBox 135deg quando passa o mouse sobre ele. Não precisamos adicionar “graus” ao valor. No menuVariants , controlamos a posição superior do Nav como você faria usando a propriedade position no CSS. Alternamos a posição superior do Nav com base no estado isOpen .

Com variantes, podemos criar relações de tempo entre componentes de movimento pai e filho. Definimos o relacionamento entre o pai Nav e seu filho, Link , usando a propriedade when no objeto de transição. Aqui, defina-o como beforeChildren , para que as animações do componente pai terminem antes do início da animação do filho.

Usando a propriedade staggerChildren , definimos uma ordem de tempo para cada link. Cada link levará 0,5 segundos para aparecer um após o outro. Isso cria uma boa dica visual quando o Nav é aberto. No linkVariants animamos a opacidade e a posição vertical de cada link.

 <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>

Aqui, passamos as variantes para seus respectivos componentes. No SvgBox , alternamos o estado de isOpen sempre que ele é clicado e o animamos condicionalmente com base no estado. Como o SvgBox , animamos condicionalmente o Nav e o Link s com base no estado de isOpen .

Modal Animado

Construiremos um componente modal e aprenderemos sobre o AnimatePresence do Framer Motion e como ele nos permite animar elementos à medida que saem do DOM.

Componentes:

  • App.js : configuramos o estado showModal aqui.
  • Modal.jsx : o trabalho de animação real ocorre aqui.
  • Styles.js : crie, estilize e exporte os componentes de movimento. Os componentes são estilizados usando styled-components.

Vamos analisar o 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> ); }

Criamos um estado showModal que será usado para renderizar condicionalmente o modal. A função toggleModal alternará o estado sempre que o ToggleButton for clicado. ToggleButton é um componente de movimento, então podemos definir animações para ele. Quando é montado, ele desliza da esquerda. Esta animação é executada por 0,5 segundos. Também passamos do estado showModal para o Modal através de props.

Agora, 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>

Importamos AnimatePresence do framer-motion . Ele nos permite definir animações de saída para componentes quando eles saem do DOM. Renderizamos condicionalmente o Modal com base no estado showModal . Definimos as animações para ModalBox e ModalContent através de seus props initial e animate . Há também um novo adereço aqui, exit . Ter AnimatePresence como um wrapper nos permite adicionar animações de saída ao ModalBox na prop de exit .

Animação de rolagem

Usaremos uma combinação do gancho useAnimation e react react-intersection-observer para criar animações acionadas por rolagem.

Componentes

  • App.js : configuramos as animações para o componente Box e renderizamos no App
  • Styles.js : crie, estilize e exporte os componentes de movimento. Os componentes são estilizados usando 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} /> ); };

O gancho useAnimation nos permite controlar as sequências em que nossas animações ocorrem. Temos acesso aos métodos controls.start e controls.stop que podemos usar para iniciar e parar manualmente nossas animações. Passamos a animação hidden inicial para StyledBox . Passamos os controles que definimos com o método start para StyledBox animate prop.

O gancho useInView do react react-intersection-observer nos permite rastrear quando um componente está visível na viewport. O hook useInView nos dá acesso a ref , que passamos para o componente que queremos assistir, e o boolean inView , que nos diz se aquele elemento é inView ou não. Usamos o useEffect para chamar controls.start sempre que o elemento que estamos observando, StyledBox , estiver em exibição. Passamos controls e inView como dependências de useEffect . Além disso, passamos as variantes que definimos, BoxVariants para StyledBox .

Animação de herói

Construiremos uma animação de banner de herói legal usando o gancho useCycle . Vamos entender como useCycle nos permite percorrer animações.

 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" }, }, };

Definimos 3 variantes, H1Variants , TextVariants e BannerVariants . No entanto, nosso foco é BannerVariants . Definimos 2 animações, animationOne e animationTwo em BannerVariants . Estas são as animações que passamos para o useCycle para percorrer.

 const [animation, cycleAnimation] = useCycle("animationOne", "animationTwo"); useEffect(() => { setTimeout(() => { cycleAnimation(); }, 2000); }, []);

useCycle funciona de maneira semelhante ao gancho useState . Na matriz desestruturada, a animation representa a animação que está ativa, seja animationOne ou animationTwo . A função cylceAnimation que alterna entre a animação que definimos. Passamos as animações que queremos percorrer em useCycle e chamamos cylceAnimation após 2 segundos em 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>

No final de tudo, passamos as variantes para seus respectivos componentes e vemos a mágica acontecer. Com isso, o Banner deslizará inicialmente da direita com base nas animações que definimos em animationOne , e após 2 segundos, cycleAnimation será chamado que acionará animationTwo .

Como um porco sábio disse uma vez, “isso é tudo pessoal”.

Conclusão

Passamos pelo básico do Framer Motion e vimos alguns projetos de demonstração que nos dão um vislumbre da variedade de animações que podemos criar. No entanto, você pode fazer muito mais com ele. Eu encorajo você a mergulhar nos documentos e enlouquecer.

Recursos

  • Documentos de API do Framer Motion, Framer Motion
  • react-intersection-observer, npm
  • Framer Motion para React, NetNinja