Presentamos Framer Motion

Publicado: 2022-03-10
Resumen rápido ↬ Las animaciones, cuando se hacen bien, son poderosas. Sin embargo, crear animaciones llamativas con CSS puede ser complicado. Llega Framer Motion. Con Framer Motion, no necesita ser un experto en CSS para crear hermosas animaciones. Framer Motion nos proporciona animaciones listas para producción y una API de bajo nivel con la que podemos interactuar para integrar estas animaciones en nuestras aplicaciones.

En este artículo, veremos más de cerca cómo Framer Motion nos ayuda a crear animaciones asombrosas. Aprenderemos cómo funcionan los componentes de movimiento y cómo encadenar animaciones. Veremos cómo hacer animaciones activadas por gestos, cronometradas y de desplazamiento con el movimiento de Framer. En el camino, usaremos lo que aprendamos para crear cinco aplicaciones de demostración que configuré para mostrarnos cómo podemos integrar Framer Motion en aplicaciones del mundo real.

Este tutorial será beneficioso para los lectores que estén interesados ​​en integrar animaciones en su aplicación React.

Nota: este artículo requiere una comprensión básica de React y CSS.

¿Qué es Framer Motion?

Framer Motion es una biblioteca de animación que facilita la creación de animaciones. Su API simplificada nos ayuda a abstraer las complejidades detrás de las animaciones y nos permite crear animaciones con facilidad.

Componentes de movimiento

Estos son los componentes básicos del movimiento de Framer. Los componentes de movimiento se crean anteponiendo motion a su elemento HTML y SVG habitual (p. ej., motion.h1 ). Los componentes de movimiento pueden aceptar varios accesorios, siendo el básico el accesorio animate . Este accesorio toma un objeto donde definimos las propiedades de ese componente que queremos animar. Las propiedades que definimos se animarán cuando el componente se monte en el DOM.

Vamos a animar un texto h1 usando Framer Motion. Primero, instalamos la biblioteca framer-motion e importamos motion .

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

Luego convertimos el h1 en un componente de movimiento.

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

Esto hará que el h1 se deslice 20 píxeles hacia la derecha y se mueva 20 píxeles hacia arriba cuando se cargue. Cuando no se agregan unidades, los cálculos se realizan usando píxeles. Sin embargo, puede establecer explícitamente las unidades en las que desea que se basen los cálculos, animate={{x: "20rem", y: "-20rem"}}> .

¡Más después del salto! Continúe leyendo a continuación ↓

De forma predeterminada, un componente de movimiento se animará desde el estado definido a partir de sus estilos a los del accesorio animate . Sin embargo, si quisiéramos, podríamos secuestrar y definir el estado de animación inicial del componente usando el apoyo initial . Mientras que la propiedad animate se usa para definir el comportamiento de los componentes cuando se montan, la propiedad initial define su comportamiento antes de que se monten.

Si queremos que nuestro h1 entre desde la izquierda, lo controlamos usando la propiedad inicial.

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

Ahora, cuando el h1 se monta, se desliza desde la izquierda.

No estamos limitados a una sola animación. Podemos definir una serie de animaciones llamadas keyframes en una matriz de valores. Cada valor se animará en secuencia.

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

El accesorio de transition nos permite definir cómo ocurren las animaciones. Con él, definimos cómo los valores se animan de un estado a otro. Entre otras cosas, podemos definir la duration , el delay y el type de animación usando este accesorio.

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

Digamos que tuviéramos que animar varios componentes de movimiento simultáneamente, como en el fragmento de código a continuación.

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

Mientras esto funciona, la propiedad de variants en Framer Motion nos permite extraer nuestras definiciones de animación en un objeto de variantes. Las variants no solo hacen que nuestro código sea más limpio, sino que también nos permiten crear animaciones aún más poderosas y complejas.

Extrayendo nuestras definiciones de animación en objetos variantes, tenemos esto:

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

En lugar de pasar las definiciones de animación a los accesorios initial y animate de un componente directamente, extraemos estas definiciones en objetos variantes independientes. En los objetos variantes, definimos nombres de variantes que describen el nombre de cada animación 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>

En el accesorio de variants , pasamos el nombre de los objetos variantes para cada componente de movimiento y luego pasamos las animaciones a los accesorios initial y animate .

Podemos llevar nuestra configuración actual con variantes más allá para reducir la repetición. Usando variantes, podemos propagar atributos de animación a través del DOM desde un componente de movimiento principal. Para que esto funcione, creamos variantes para el archivo principal motion.div con nombres de animación similares en su objeto variante como sus elementos secundarios. Al hacer esto, no tendremos que pasar los nombres de las animaciones a cada componente secundario. Detrás de escena, el elemento padre maneja eso por nosotros.

 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>

Ahora tenemos un código más limpio sin repeticiones. Convertimos el contenedor div en un componente de movimiento para poder pasar el objeto ContainerVariants que definimos. Como no definimos ninguna animación en el contenedor, pasamos objetos vacíos a initial y animate . Los nombres de las animaciones deben ser los mismos en todos los objetos variantes para que funcione la propagación.

Ahora entendemos los conceptos básicos de Framer Motion. Comencemos a construir nuestra primera de 5 aplicaciones de demostración.

Tienda de iconos

Podemos crear animaciones interactivas a base de gestos. Los componentes de movimiento actualmente pueden escuchar la detección de gestos de desplazamiento, toque, panorámica y arrastre. Construiremos esta aplicación Icon Shop usando el accesorio whileHover .

Componentes

  • App.js : contiene los textos de encabezado.
  • Card.jsx : aquí definimos las animaciones para las tarjetas de iconos.
  • CardContainer.jsx : importamos y recorremos los íconos.
  • styles.js : cree, aplique estilo y exporte los componentes de movimiento. Usé componentes con estilo para diseñar los componentes.

Comencemos con 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 los componentes de movimiento H1 y H2 que creamos en el archivo Styles.js . Dado que son componentes de movimiento, usamos los apoyos initial y animate para definir su comportamiento antes y cuando se montan. Aquí, también importamos y mostramos el componente CardContiner .

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

Aquí, importamos los SVG, el componente de movimiento del Container y el componente de la Card .

Similar a H1 y H2 en App.js , definimos animaciones del Container usando los accesorios initial y animate . Cuando se cargue, creará un efecto genial al deslizarse desde la izquierda del navegador.

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

Aquí, creamos dos objetos variantes con beforeHover y onHover . En el objeto CardVariants , no queremos hacer nada inicialmente, por lo que beforeHover es un objeto vacío. onHover aumentamos la escala de la caja de la tarjeta.

En el objeto IconVariants , definimos el estado inicial del IconBox en su beforeHover . Establecemos su opacidad en 0 y lo empujamos hacia arriba en 50px. Luego, en onHover , volvemos a establecer la opacidad en 1, la empujamos a su posición predeterminada y cambiamos el tipo de transición a tween . Luego pasamos las variantes a sus respectivos componentes de movimiento. Hacemos uso de la propagación, por lo que no necesitamos establecer explícitamente los accesorios initial y animate en el componente IconBox .

Barra de navegación animada

Construiremos un componente de navegación simple y veremos cómo podemos crear relaciones de tiempo entre los componentes de movimiento principales y secundarios.

Componentes

  • App.js : contiene los textos de encabezado.
  • Styles.js : crea, diseña y exporta los componentes de movimiento. Los componentes se diseñan utilizando componentes con estilo.

Echemos un vistazo al archivo 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 } };

Creamos un estado isOpen que se usará para verificar si la barra de navegación está abierta o no. Creamos 3 objetos variantes, iconVariants , menuVariants y linkVariants donde definimos las animaciones para los componentes de movimiento SvgBox , Nav y Link respectivamente. El iconVariants se usa para rotar el SvgBox grados cuando se desplaza sobre él. No necesitamos agregar "grados" al valor. En menuVariants , controlamos la posición superior de Nav como lo harías usando la propiedad de position en CSS. Alternamos la posición superior de Nav en función del estado isOpen .

Con las variantes, podemos crear relaciones de tiempo entre los componentes de movimiento principales y secundarios. Definimos la relación entre el padre Nav y su hijo Link usando la propiedad when en el objeto de transición. Aquí, configúrelo en beforeChildren , para que las animaciones del componente principal terminen antes de que comience la animación del elemento secundario.

Usando la propiedad staggerChildren , establecemos un orden de tiempo para cada enlace. Cada enlace tardará 0,5 segundos en aparecer uno tras otro. Esto crea una buena señal visual cuando se abre Nav . En linkVariants la opacidad y la posición vertical de cada enlace.

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

Aquí, pasamos las variantes a sus respectivos componentes. En SvgBox , alternamos el estado de isOpen cada vez que se hace clic en él, luego lo animamos condicionalmente según el estado. Al igual que SvgBox , animamos condicionalmente Nav y Link s en función del estado de isOpen .

Modal animado

Construiremos un componente modal y aprenderemos sobre AnimatePresence de AnimatePresence Motion y cómo nos permite animar elementos a medida que salen del DOM.

Componentes:

  • App.js : configuramos el estado showModal aquí.
  • Modal.jsx : el trabajo de animación real se lleva a cabo aquí.
  • Styles.js : crea, diseña y exporta los componentes de movimiento. Los componentes se diseñan utilizando componentes con estilo.

Echemos un vistazo a 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> ); }

Creamos un estado showModal que se usará para representar condicionalmente el modal. La función toggleModal alternará el estado cada vez que se haga clic en ToggleButton . ToggleButton es un componente de movimiento, por lo que podemos definir animaciones para él. Cuando se monta, se desliza desde la izquierda. Esta animación dura 0,5 segundos. También pasamos del estado showModal al Modal mediante props.

Ahora, 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 desde framer framer-motion . Nos permite establecer animaciones de salida para los componentes cuando salen del DOM. Representamos condicionalmente el Modal en función del estado showModal . Definimos las animaciones para ModalBox y ModalContent a través de sus accesorios initial y animate . También hay un nuevo accesorio aquí, exit . Tener AnimatePresence como contenedor nos permite agregar animaciones de salida a ModalBox en la propiedad de exit .

Animación de desplazamiento

Usaremos una combinación del gancho useAnimation y react-intersection-observer para crear animaciones activadas por desplazamiento.

Componentes

  • App.js : configuramos las animaciones para el componente Box y las renderizamos en la App
  • Styles.js : crea, diseña y exporta los componentes de movimiento. Los componentes se diseñan utilizando componentes con estilo.
 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} /> ); };

El gancho useAnimation nos permite controlar las secuencias en las que ocurren nuestras animaciones. Tenemos acceso a los métodos controls.start y controls.stop que podemos usar para iniciar y detener manualmente nuestras animaciones. Pasamos la animación hidden inicial a StyledBox . Pasamos los controles que definimos con el método de start a StyledBox animate prop.

El gancho useInView react-intersection-observer nos permite rastrear cuándo un componente está visible en la ventana gráfica. El gancho useInView nos da acceso a ref , que le pasamos al componente que queremos ver, y el booleano inView , que nos dice si ese elemento es inView o no. Usamos useEffect para llamar a controls.start cada vez que el elemento que estamos viendo, StyledBox , está a la vista. Pasamos controls e inView como dependencias de useEffect . Además, pasamos las variantes que definimos, BoxVariants a StyledBox .

Animación de héroe

Construiremos una genial animación de banner de héroe usando el gancho useCycle . Entenderemos cómo useCycle nos permite recorrer las animaciones.

 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 y BannerVariants . Sin embargo, nuestro enfoque es BannerVariants . Definimos 2 animaciones, animationOne y animationTwo en BannerVariants . Estas son las animaciones que pasamos al useCycle para recorrerlas.

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

useCycle funciona de manera similar al gancho useState . En la matriz desestructurada, animation representa la animación que está activa, ya sea animationOne o animationTwo . La función cylceAnimation que alterna entre la animación que definimos. Pasamos las animaciones que queremos recorrer en useCycle y llamamos a cylceAnimation después de 2 segundos en 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>

Al final de todo, pasamos las variantes a sus respectivos componentes y vemos cómo sucede la magia. Con esto, el Banner se deslizará inicialmente desde la derecha en función de las animaciones que definimos en animationOne , y después de 2 segundos, se llamará a cycleAnimation , lo que activará animationTwo .

Como dijo una vez un cerdo sabio, "eso es todo amigos".

Conclusión

Hemos repasado los conceptos básicos de Framer Motion y hemos visto algunos proyectos de demostración que nos dan una idea de la variedad de animaciones que podemos crear. Sin embargo, puedes hacer mucho más con él. Te animo a que te sumerjas en los documentos y te vuelvas loco.

Recursos

  • Framer Motion Api Docs, Framer Motion
  • reaccionar-intersección-observador, npm
  • Framer Motion para React, NetNinja