Présentation de Framer Motion
Publié: 2022-03-10Dans cet article, nous examinerons de plus près comment Framer Motion nous aide à créer des animations impressionnantes. Nous apprendrons comment fonctionnent les composants de mouvement et comment enchaîner des animations. Nous verrons comment créer des animations déclenchées par des gestes, chronométrées et de défilement avec le mouvement Framer. En cours de route, nous utiliserons les choses que nous apprenons pour créer cinq applications de démonstration que j'ai configurées pour nous montrer comment nous pouvons intégrer Framer Motion dans des applications du monde réel.
Ce tutoriel sera utile aux lecteurs intéressés par l'intégration d'animations dans leur application React.
Remarque : cet article nécessite une compréhension de base de React et CSS.
Qu'est-ce que le mouvement Framer ?
Framer Motion est une bibliothèque d'animations qui facilite la création d'animations. Son API simplifiée nous aide à résumer les complexités derrière les animations et nous permet de créer facilement des animations.
Composants de mouvement
Ce sont les éléments constitutifs du mouvement Framer. Les composants de mouvement sont créés en préfixant motion
à votre élément HTML et SVG habituel (par exemple, motion.h1
). Les composants de mouvement peuvent accepter plusieurs accessoires, le principal étant l'accessoire animate
. Cet accessoire prend un objet où nous définissons les propriétés de ce composant que nous voulons animer. Les propriétés que nous définissons seront animées lorsque le composant sera monté dans le DOM.
Animons un texte h1 à l'aide de Framer Motion. Tout d'abord, nous installons la bibliothèque framer-motion et importons motion
.
npm i framer-motion import { motion } from 'framer-motion';
Ensuite, nous convertissons le h1 en une composante de mouvement.
<motion.h1 animate={{x: 20, y: -20}}> This is a motion component </motion.h1>
Cela fera glisser le h1
de 20 pixels vers la droite et se déplacera de 20 pixels vers le haut lors du chargement. Lorsque les unités ne sont pas ajoutées, les calculs sont effectués à l'aide de pixels. Cependant, vous pouvez définir explicitement les unités sur lesquelles vous souhaitez que les calculs soient basés, animate={{x: "20rem", y: "-20rem"}}>
.
Par défaut, un composant de mouvement sera animé de l'état défini à partir de ses styles à ceux de l'accessoire d' animate
. Cependant, si nous le voulions, nous pourrions détourner et définir l'état d'animation initial du composant à l'aide de l'accessoire initial
. Alors que l'accessoire animate
est utilisé pour définir le comportement des composants lorsqu'ils sont montés, l'accessoire initial
définit leur comportement avant leur montage.
Si nous voulons que notre h1 vienne de la gauche, nous contrôlons cela en utilisant la prop initiale.
<motion.h1 initial={{x: -1000}} animate={{x: 20}}> This is a motion component </motion.h1>
Maintenant, lorsque le h1
est monté, il se glisse à partir de la gauche.
Nous ne sommes pas limités à une seule animation. Nous pouvons définir une série d'animations appelées keyframes
dans un tableau de valeurs. Chaque valeur sera animée en séquence.
<motion.h1 initial={{x: -1000}} animate={{x: [20, 50, 0, -70, 40] }}> This is a motion component </motion.h1>
L'accessoire de transition
nous permet de définir comment les animations se produisent. Avec lui, nous définissons comment les valeurs s'animent d'un état à un autre. Entre autres choses, nous pouvons définir la duration
, le delay
et type
d'animation à l'aide de cet accessoire.
<motion.h1 initial={{ x: -1000 }} animate={{ x: 0 }} transition={{ type: "tween", duration: "2", delay: "1" }}> This is a motion component </motion.h1>
Supposons que nous devions animer plusieurs composants de mouvement simultanément, comme dans l'extrait de code ci-dessous.
<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>
Pendant que cela fonctionne, le prop variants
dans Framer Motion nous permet d'extraire nos définitions d'animation dans un objet variants. Non seulement les variants
rendent notre code plus propre, mais elles nous permettent de créer des animations encore plus puissantes et complexes.
En extrayant nos définitions d'animation dans des variantes d'objets, nous avons ceci :
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" } }
Au lieu de transmettre directement les définitions d'animation dans les accessoires initial
et animate
d'un composant, nous extrayons ces définitions dans des objets variants autonomes. Dans les objets variantes, nous définissons des noms de variantes qui décrivent le nom de chaque animation en tant que 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>
Dans les accessoires de variants
, nous passons le nom des objets de variante pour chaque composant de mouvement, puis passons les animations aux accessoires initial
et animate
.
Nous pouvons aller plus loin dans notre configuration actuelle avec des variantes pour réduire les répétitions. En utilisant des variantes, nous pouvons propager les attributs d'animation dans le DOM à partir d'un composant de mouvement parent. Pour que cela fonctionne, nous créons des variantes pour le parent motion.div
avec des noms d'animation similaires dans son objet variant comme ses enfants. En faisant cela, nous n'aurons pas à transmettre les noms d'animation à chaque composant enfant. Dans les coulisses, l'élément parent gère cela pour nous.
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>
Nous avons maintenant un code plus propre sans répétitions. Nous avons transformé le conteneur div en un composant de mouvement afin de pouvoir transmettre l'objet ContainerVariants
que nous avons défini. Comme nous ne définissons aucune animation sur le conteneur, nous passons des objets vides à initial
et animate
. Vos noms d'animation doivent être les mêmes dans chaque objet variante pour que la propagation fonctionne.
Nous comprenons maintenant les bases de Framer Motion. Commençons à construire notre poing de 5 applications de démonstration.
Boutique d'icônes
Nous pouvons créer des animations interactives basées sur des gestes. Les composants de mouvement sont actuellement capables d'écouter la détection des gestes de survol, de toucher, de panoramique et de glissement. Nous allons construire cette application Icon Shop en utilisant le prop whileHover
.
Composants
-
App.js
: cela contient les textes d'en-tête. -
Card.jsx
: ici, nous définissons les animations pour les cartes d'icônes. -
CardContainer.jsx
: nous importons et parcourons les icônes. -
styles.js
: créez, stylisez et exportez les composants de mouvement. J'ai utilisé des composants stylés pour styliser les composants.
Commençons par 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> );
Nous importons les composants de mouvement H1
et H2
que nous avons créés dans le fichier Styles.js
. Puisqu'il s'agit de composants de mouvement, nous utilisons les accessoires initial
et animate
pour définir leur comportement avant et lorsqu'ils sont montés. Ici, nous importons et affichons également le composant CardContiner
.
Maintenant, le 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> ); };
Ici, nous importons les SVG, le composant de mouvement Container
et le composant Card
.
Semblable à H1
et H2
dans App.js
, nous définissons les animations du Container
à l'aide des accessoires initial
et animate
. Lorsqu'il se charge, il crée un effet sympa de glissement depuis la gauche du navigateur.
Maintenant, 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> ); };
Ici, nous créons deux variantes d'objets avec des animations beforeHover
et onHover
. Dans l'objet CardVariants
, nous ne voulons rien faire au départ, donc beforeHover
est un objet vide. onHover
nous augmentons l'échelle de la boîte à cartes.
Dans l'objet IconVariants
, nous définissons l'état initial de l' IconBox
dans son beforeHover
. Nous définissons son opacité sur 0 et la poussons vers le haut de 50px. Ensuite, dans onHover
, nous remettons l'opacité à 1, la repoussons à sa position par défaut et changeons le type de transition en tween
. Puis on passe dans les variantes à leurs composantes de mouvement respectives. Nous utilisons la propagation, nous n'avons donc pas besoin de définir explicitement les accessoires initial
et animate
sur le composant IconBox
.
Barre de navigation animée
Nous allons créer un composant de navigation simple et nous verrons comment créer des relations temporelles entre les composants de mouvement parents et enfants.
Composants
-
App.js
: cela contient les textes d'en-tête. -
Styles.js
: créez, stylisez et exportez les composants de mouvement. Les composants sont stylisés à l'aide de styled-components.
Examinons le fichier 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 } };
Nous créons un état isOpen
qui sera utilisé pour vérifier si la barre de navigation est ouverte ou non. Nous créons 3 objets variantes, iconVariants
, menuVariants
et linkVariants
où nous définissons les animations pour les composants de mouvement SvgBox
, Nav
et Link
respectivement. L' iconVariants
est utilisé pour faire pivoter la SvgBox
de 135 degrés lorsqu'elle est survolée. Nous n'avons pas besoin d'ajouter "deg" à la valeur. Dans le menuVariants
, nous contrôlons la position supérieure du Nav
comme vous le feriez en utilisant la propriété position
en CSS. Nous basculons la position supérieure de la Nav
en fonction de l'état isOpen
.
Avec les variantes, nous pouvons créer des relations temporelles entre les composants de mouvement parents et enfants. Nous définissons la relation entre le parent Nav
et son enfant, Link
en utilisant la propriété when
dans l'objet transition. Ici, définissez-le sur beforeChildren
, afin que les animations du composant parent se terminent avant le début de l'animation de l'enfant.
À l'aide de la propriété staggerChildren
, nous définissons un ordre de synchronisation pour chaque lien. Chaque lien mettra 0,5 seconde à apparaître l'un après l'autre. Cela crée un joli repère visuel lorsque le Nav
est ouvert. Dans les linkVariants
nous animons l'opacité et la position verticale de chaque lien.
<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>
Ici, on passe dans les variantes à leurs composants respectifs. Dans le SvgBox
, nous basculons l'état de isOpen
chaque fois qu'il est cliqué, puis l'animons conditionnellement en fonction de l'état. Comme le SvgBox
, nous animons conditionnellement les Nav
et les Link
en fonction de l'état de isOpen
.
Modal animé
Nous allons construire un composant modal et découvrir AnimatePresence de AnimatePresence
Motion, et comment il nous permet d'animer des éléments lorsqu'ils quittent le DOM.
Composants:
-
App.js
: nous configurons ici l'étatshowModal
. -
Modal.jsx
: le travail d'animation proprement dit a lieu ici. -
Styles.js
: créez, stylisez et exportez les composants de mouvement. Les composants sont stylisés à l'aide de styled-components.
Examinons 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> ); }
Nous créons un état showModal
qui sera utilisé pour restituer conditionnellement le modal. La fonction toggleModal
basculera l'état chaque fois que le ToggleButton
est cliqué. ToggleButton
est un composant de mouvement, nous pouvons donc définir des animations pour celui-ci. Lorsqu'il est monté, il se glisse par la gauche. Cette animation dure 0,5 seconde. Nous passons également de l'état showModal
au Modal
via les props.
Maintenant, 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>
Nous importons AnimatePresence
de framer framer-motion
. Cela nous permet de définir des animations de sortie pour les composants lorsqu'ils quittent le DOM. Nous rendons conditionnellement le Modal
en fonction de l'état showModal
. Nous définissons les animations pour ModalBox
et ModalContent
à travers leurs accessoires initial
et animate
. Il y a aussi un nouvel accessoire ici, exit
. Avoir AnimatePresence
comme wrapper nous permet d'ajouter des animations de sortie à ModalBox
dans le prop de exit
.
Animation de défilement
Nous utiliserons une combinaison du crochet useAnimation
et de react react-intersection-observer
pour créer des animations déclenchées par défilement.
Composants
-
App.js
: nous configurons les animations pour le composantBox
et le rendons dansApp
-
Styles.js
: créez, stylisez et exportez les composants de mouvement. Les composants sont stylisés à l'aide de 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} /> ); };
Le crochet useAnimation
nous permet de contrôler les séquences dans lesquelles nos animations se produisent. Nous avons accès aux méthodes controls.start
et controls.stop
que nous pouvons utiliser pour démarrer et arrêter manuellement nos animations. Nous passons l'animation hidden
initiale à StyledBox
. Nous transmettons les contrôles que nous avons définis avec la méthode start
à StyledBox
animate prop.
Le crochet useInView
de react react-intersection-observer
nous permet de savoir quand un composant est visible dans la fenêtre. Le crochet useInView
nous donne accès à ref
, que nous transmettons au composant que nous voulons surveiller, et le booléen inView
, qui nous indique si cet élément est inView
ou non. Nous utilisons useEffect
pour appeler controls.start
chaque fois que l'élément que nous regardons, StyledBox
est en vue. Nous transmettons controls
et inView
en tant que dépendances de useEffect
. De plus, nous transmettons les variantes que nous avons définies, BoxVariants
à StyledBox
.
Animation de héros
Nous allons créer une animation de bannière de héros cool à l'aide du crochet useCycle
. Nous comprendrons comment useCycle
nous permet de faire défiler les animations.
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" }, }, };
Nous définissons 3 variantes, H1Variants
, TextVariants
et BannerVariants
. Cependant, nous nous concentrons sur BannerVariants
. Nous définissons 2 animations, animationOne
et animationTwo
dans BannerVariants
. Ce sont les animations que nous passons dans useCycle
pour les parcourir.
const [animation, cycleAnimation] = useCycle("animationOne", "animationTwo"); useEffect(() => { setTimeout(() => { cycleAnimation(); }, 2000); }, []);
useCycle
fonctionne de la même manière que le crochet useState
. Dans le tableau déstructuré, animation
représente l'animation active, qu'il s'agisse animationOne
ou animationTwo
. La fonction cylceAnimation
qui alterne entre les animations que nous avons définies. Nous passons les animations que nous voulons parcourir dans useCycle
et appelons cylceAnimation
après 2 secondes dans 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 fin de tout, nous passons les variantes à leurs composants respectifs et regardons la magie opérer. Avec cela, la Banner
glissera initialement à partir de la droite en fonction des animations que nous avons définies dans animationOne
, et après 2 secondes, cycleAnimation
sera appelée, ce qui déclenchera animationTwo
.
Comme l'a dit un jour un cochon sage, "c'est tout."
Conclusion
Nous avons parcouru les bases de Framer Motion et vu quelques projets de démonstration qui nous donnent un aperçu de la gamme d'animations que nous pouvons créer. Cependant, vous pouvez faire bien plus avec. Je vous encourage à vous plonger dans la documentation et à vous déchaîner.
Ressources
- Framer Motion Api Docs, Framer Motion
- réagir-intersection-observateur, npm
- Framer Motion pour React, NetNinja