Présentation de Framer Motion

Publié: 2022-03-10
Résumé rapide ↬ Les animations, lorsqu'elles sont bien faites, sont puissantes. Cependant, créer des animations accrocheuses avec CSS peut être délicat. Arrive Framer Motion. Avec Framer Motion, vous n'avez pas besoin d'être un expert CSS pour créer de belles animations. Framer Motion nous fournit des animations prêtes pour la production et une API de bas niveau avec laquelle nous pouvons interagir pour intégrer ces animations dans nos applications.

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

Plus après saut! Continuez à lire ci-dessous ↓

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'état showModal .
  • 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 composant Box et le rendons dans App
  • 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