Presentamos Framer Motion
Publicado: 2022-03-10En 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"}}>
.
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 estadoshowModal
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 componenteBox
y las renderizamos en laApp
-
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