AccueilClients

Applications et sites

  • Application métiersIntranet, back-office...
  • Applications mobilesAndroid & iOS
  • Sites InternetSites marketings et vitrines
  • Expertises techniques

  • React
  • Expo / React Native
  • Next.js
  • Node.js
  • Directus
  • TypeScript
  • Open SourceBlogContactEstimer

    1 décembre 2020

    Framer Motion : Animez vos applications en toute simplicité

    14 minutes de lecture

    Framer Motion : Animez vos applications en toute simplicité

    En matière de design web et mobile, le mouvement est aujourd’hui la norme. Animation à l’activation d’un bouton, interactions liées au scroll, ou navigation entre les pages : les applications prennent vie et deviennent beaucoup plus intuitives et ludiques pour les utilisateurs. Pour ne pas se perdre dans un cauchemar d’animations CSS illisibles, bien s’équiper est essentiel. C’est là que Framer Motion entre en action.

    1. Framer Motion, c’est quoi ?

    Framer Motion est une librairie d’animation de composants. Cette bibliothèque met à disposition une API simple d’utilisation, permettant de gérer en quelques props :

    • l’animation des composants
    • leur réaction aux mouvements de l’utilisateur
    • les interactions entre les mouvements de différents composants (rythme, priorité d'exécution)
    • les animations à la suppression des composants du DOM

    Bref un menu bien complet.

    2. Notre Projet

    Pour évaluer les capacités de cette bibliothèque, nous allons créer un livre animé. Noël arrive, on va donc retomber en enfance pour un temps avec “Alice au Pays des Merveilles”. Pour ceux qui veulent passer directement à l'animation, le starter kit est disponible en dessous.

    C’est parti :

    La base de l'app

    La base de notre app sera en Next.js

    npx create-next-app
    

    On ajoutera aussi Typescript

    yarn add --dev typescript @types/react @types/node
    

    _app.js et index.js deviennent du coup _app.tsx et index.tsx

    Bien entendu nous avons besoin de framer motion

    yarn add framer-motion
    

    Et nous utiliserons également la librairie de composants Chakra Ui pour structurer facilement nos pages. Framer Motion fait d'ailleurs partie des packages obligatoires à l'installation depuis la v1 :

    yarn add @chakra-ui/react @emotion/react @emotion/styled
    

    On ajoute le Provider Chakra à notre app :

    // _app.tsx
    import { ChakraProvider } from "@chakra-ui/react"
    ...
      return (
        <ChakraProvider>
          <App />
        </ChakraProvider>
      )
    
    

    Le contenu de l’app

    Il nous faut aussi récupérer les deux premiers chapitres du texte de notre livre.

    Chaque chapitre sera composé :

    • d’une illustration principale pour la page d’accueil
    • d’un titre et sous titre
    • et de pages contenant elle même des Paragraphes et possiblement une animation à afficher en haut ou en bas de la page

    Notre app a donc besoin de 3 composants principaux :

    • un composant Chapter qui affiche les chapitres du livre
    • un composant Page qui affiche les paragraphes de chaque page du chapitre
    • un composant BookNav qui permet de naviguer entre les pages et les chapitres

    Starter Kit

    Une fois tout ça mis en place, on a notre app prête à animer : STARTER KIT

    Pour démarrer le projet :

    yarn dev
    

    3. A l’origine du mouvement : le composant Motion

    La doc officielle sur Motion

    Tout mouvement avec Framer Motion repose sur un composant motion. Il existe un composant motion pour chaque élément HTML ainsi que pour les SVG .

    <motion.div></motion.div>
    <motion.h1></motion.h1>
    

    Ce composant accepte deux propriétés :

    • initial : l’état du composant avant l’animation - son aspect d’origine
    • animate : l’état du composant après animation - son aspect final

    Pour sélectionner le type de transformation à appliquer on passe à initial et animate un objet contenant les propriétés css que l’on souhaite manipuler. Framer Motion met également à disposition des propriétés supplémentaires pour faciliter les transformations :

    • x et y : la position horizontale et verticale de l’élément sur la page, 0 étant sa position naturelle
    • scale et rotate : pour l’échelle et la rotation de l’élément

    Commençons par une animation simple du titre du livre sur index.tsx avec un composant <motion.div> :

    // index.tsx
    import { motion } from 'framer-motion'
    ...
         <motion.div
           initial={{ opacity: 0, y: '-100px' }}
           animate={{ opacity: 1, y: 0 }}
         >
           <Text textAlign="center" fontSize="3xl" p={4}>
             {book.title}
           </Text>
         </motion.div>
    
    

    Sur la page d’accueil le titre du livre va maintenant automatiquement se déplacer horizontalement de haut en bas, et augmenter progressivement sa visibilité. Chakra Ui, de son côté, offre aussi la possibilité de combiner la variété des animations framer motion avec la simplicité de l'intégration de ses composants modulaires en 1 ligne seulement :

    import { Box } from '@chakra-ui/react'
    import { motion } from 'framer-motion'
    
    // 1. Create a custom motion component from Box
    const MotionBox = motion.custom(Box)
    
    // 2. You'll get access to `motion` and `chakra` props in `MotionBox`
    function Example() {
      return (
        <MotionBox
          boxSize="40px"
          bg="red.300"
          drag="x"
          dragConstraints={{ left: -100, right: 100 }}
          whileHover={{ scale: 1.1 }}
          whileTap={{ scale: 0.9 }}
        />
      )
    }
    

    Et voilà! Un composant au style totalement modulable, agrémenté de nouvelles props pour l'animation. Pour ce premier tutoriel, nous conserverons la notation <motion.div> pour mieux nous retrouver dans la doc officielle. Mais les applications de cette combinaison Chakra Ui + framer motion sont très prometteuses.

    Notre animation fonctionne mais elle est trop rapide, nous avons besoin d’un mouvement plus lent. C’est là qu’intervient la propriété transition.

    4. Transition : Régler plus finement le type d’animation sur nos composants

    La doc officielle sur transition

    La propriété transition permet de préciser au composant motion la façon dont il doit gérer l’animation. Comme initial et animate il accepte un objet pour paramétrer la transition. Les propriétés de cet objet sont appelées des propriétés d'orchestration. Nous allons essentiellement utiliser :

    • delay : le nombre de secondes à attendre avant de lancer l’animation
    • duration : le nombre de secondes sur lesquelles l’animation va se dérouler
    • repeat : combien de fois répéter l’animation (un chiffre ou Infinity pour un mouvement perpétuel)
    • repeatType : "loop" | "reverse" | "mirror" la façon dont on répètera l’animation

    Notre animation étant trop rapide nous pouvons lui passer une propriété duration de 1.5 pour la ralentir :

    // index.tsx
    
    <motion.div
      initial={{ opacity: 0, y: '-100px' }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 1.5 }}
    >
      <Text textAlign="center" fontSize="3xl" p={4}>
        {book.title}
      </Text>
    </motion.div>
    

    Le mouvement est maintenant plus fluide :

    Transition se base sur 3 types de mouvements pour réaliser les animations :

    • spring : qui anime l’élément en le faisant légèrement rebondir
    • tween : qui utilise un mouvement plus uniforme
    • intertia : basé sur un mouvement qui décélère progressivement

    Les types de transitions par défaut

    Transition accepte un paramètre type pour spécifier le type de mouvement à appliquer. Cependant certaines propriétés d’animations définies sur animate et initial induiront certains mouvements par défaut :

    • les modifications des propriétés physiques X et Y utilisent spring par défaut
    • les changements d’opacité ou de couleur passent eux par tween

    Chaque type d’animation acceptera des paramètres d’orchestration différents. Les possibilités de combinaisons sont infinies :

    Testons les possibilités avec spring. Quoi de mieux qu’un lapin pour bondir ? Nous allons donner du mouvement à l’illustration du lapin visible sur /preview :

    // BouncingBunny.tsx
    
    import { motion } from 'framer-motion'
    
    const BoucingBunny = () => {
      return (
        <Center p={10}>
          <motion.div
            initial={{ y: '-5vh' }}
            animate={{ y: 0 }}
            transition={{
              type: 'spring',
              repeat: Infinity,
              duration: 2,
              bounce: 0.7,
              repeatType: 'reverse',
            }}
          >
            <Image src="/alice/bunny.png" maxH="200px" />
          </motion.div>
        </Center>
      )
    }
    

    Ici on a fait varier bounce qui représente l'élasticité de notre composant par défaut à 0.25. Le lapin rebondira d’autant plus que cette valeur sera élevée. L’animation boucle grâce à repeat: Infinity. Et le mouvement est plus naturel en passant le repeatType à reverse car le point d’origine de l’animation s’adapte alors à chaque tour.

    Il existe un grand nombre de paramètres d’orchestration applicables à spring. On peut régler en fonction des besoins la raideur du mouvement (stiffness) son amortissement (damping) ou la masse de l’objet (mass) pour faire varier l’aspect du rebond.

    5. Keyframes : Créer une animation progressive

    La doc officielle sur keyFrames

    Bien, nos animations font maintenant passer nos composants d’un état à l’autre, en suivant des paramètres de transition spécifiques. Mais comment fait-on quand on a besoin d’animations plus complexes ? si par exemple nous avons besoin de donner des états intermédiaires à notre composant entre la valeur initiale et la valeur finale ?

    Au lieu de spécifier une valeur simple à une propriété de animate on peut passer un array de valeurs. Le composant passera alors par tous ces états au fil de l’animation. Essayons les Keyframes sur une nouvelle animation, AliceFalling.tsx :

    // AliceFalling.tsx
    
    import { motion } from 'framer-motion'
    
    const fallingVariant = {
      visible: {
        rotate: [-100, 100, -10, 120, 80, 360],
        transition: {
          ease: 'easeInOut',
          duration: 5,
          repeat: 3,
          repeatType: 'reverse',
        },
      },
    }
    
    const AliceFalling = () => {
      return (
        <Center p={10}>
          <motion.div variants={fallingVariant} animate="visible">
            <Image src="/alice/aliceFalling.png" maxH="200px" />
          </motion.div>
        </Center>
      )
    }
    

    Les Keyframes alternants font évoluer le mouvement du personnage de manière beaucoup moins linéaire qu’un simple animate.

    Les keyframes sont par défaut espacés régulièrement au long de l’animation. Si on souhaite une exécution moins linéaire, on peut passer à transition, une propriété times associée à un array de valeur entre 0 et 1 de la même longueur que celui des keyframes. Chaque valeur de times correspondra alors au moment où l’animation le keyframe devra s’activer.

    6. Variants : Structurer et refactoriser nos animations

    La doc officielle sur variants

    Nous savons maintenant appliquer des animations à nos composants. Mais passer systématiquement toutes les propriétés transition / initial / animate à chaque composant alourdit visuellement beaucoup notre code. Ce n'est pas une solution très optimisée :

    • On ne peut pas réutiliser le même effet sur plusieurs composants
    • Il est impossible de contrôler le déroulement d’animations imbriquées
    • Enfin nous ne pouvons pas utiliser des paramètres dynamiques pour les animations

    Bref pas très pratique dès qu’on commence à avoir besoin de plusieurs animations ou de mouvements plus complexes...

    Les Variants à la rescousse!

    Une variant permet d’extraire la logique d’animation du composant dans une constante. On passe ensuite directement la variant au composant motion et on lui indique le nom des propriétés qui correspondent à son état initial ou animé. Les transitions sont alors incluses dans l’objet correspondant à l'état animate (ici visible) et s’appliqueront automatiquement. Plus besoin de spécifier cette prop séparemment.

    Créons une nouvelle animation pour tester les variants dans KeyOnDoor.tsx (visible sur /preview) :

    // KeyOnDoor.tsx
    
    import { motion } from 'framer-motion'
    
    const keyVariant = {
      hidden: {
        y: '50px',
        opacity: 0,
        rotate: 0,
      },
      visible: {
        y: '-100px',
        opacity: 1,
        rotate: 180,
        transition: {
          duration: 6,
          repeat: Infinity,
          repeatType: 'mirror',
        },
      },
    }
    
    const KeyOnDoor = () => {
      return (
        <Center p={12} flexDirection="column">
          <Image src="/alice/porte.png" maxH="200px" />
          <motion.div variants={keyVariant} animate="visible" initial="hidden">
            <Image src="/alice/cle.png" maxH="100px" />
          </motion.div>
        </Center>
      )
    }
    

    Animation parent Animations enfants - propagation et orchestration

    Dans le cas d’animations imbriquées, on peut même se passer de spécifier au composant motion enfant le nom de ses propriétés initial et animate, elles seront automatiquement transmises depuis l’animation parent.

    Par défaut, toutes les animations se lancent au même moment. Mais en utilisant les variants on a accès à des propriétés de transition bonus pour contrôler l’execution des animations enfant :

    • when : beforeChildren | afterChildren = quand l’animation du composant doit-elle avoir lieu ? Avant ou après l'exécution des animations des composants enfants?
    • delayChildren : le temps en secondes avant de lancer les animations enfants
    • staggerChildren : (stagger = échelonner) le temps de délais en secondes à appliquer entre chaque exécution d’animations enfants

    Pour le moment l’affichage de nos pages et de nos chapitres n’est pas très attrayant. Essayons d’imbriquer des animations sur ces deux composants :

    • au chargement du chapitre le titre et le sous-titre glisseront par le haut
    • à chaque changement de page le texte apparaîtra par opacité progressive
    • le titre et le sous titre du chapitre doivent s’afficher avant le contenu de la page

    On commence par le composant Page.tsx, avec notre animation enfant. Ici plus besoin de spécifier initial et animate sur le composant motion, nous les définirons sur l’animation parent. La variant suffit donc :

    // Page.tsx
    
    import { motion } from 'framer-motion'
    
    const pageVariant = {
     visible: {
       opacity: 1,
       transition: {
         duration: 1,
       },
     },
     hidden: { opacity: 0 },
    }
    
    const Page = ({ animation, paragraphs, type }: IPage) => {
     const AnimationComponent = animation?.component
     return (
       <Box minH="70vh">
         <motion.div variants={pageVariant}>
    

    Sur Chapter.tsx maintenant, l'animation parent :

    // Chapter.tsx
    import { motion } from 'framer-motion'
    
    const chapterVariant = {
      visible: {
        y: 0,
        transition: {
          duration: 1,
          when: 'beforeChildren',
        },
      },
      hidden: { y: '-20vh' },
    }
    

    beforeChildren s’assurera que l’animation du titre du chapitre est bien terminée avant de lancer les animations enfants des Pages pour l’affichage du texte. On ajoute le composant motion et on précise bien ici les props variants / initial / animate

    // Chapter.tsx
    return (
       <Box paddingBottom={20}>
         <motion.div variants={chapterVariant} initial="hidden" animate="visible">
    

    6. Gestures : Réagir aux interactions de l’utilisateur

    Nos animations commencent à être plus complexes, mais elles ne réagissent pas encore au comportement de l’utilisateur. Le composant motion met à disposition deux propriétés utiles pour interagir avec les mouvements :

    • whileHover : qui se déclenche quand le pointer passe par dessus le composant
    • whileTap : qui s’active quand l’utilisateur presse et relache le composant

    Appliquons une animation au hover et au tap sur les cartes de la page d’accueil sur index.tsx. Nous avions précédemment ajouté une animation sur le titre du livre sur cette même page. Maintenant que nous avons vu les variants, on peut extraire l’animation du titre et utiliser les effets d’orchestration parent/enfant dont nous avions parlé :

    • when:”beforeChildren” pour afficher le titre avant les cartes
    • staggerChildren:1 pour afficher chaque carte l’une après l’autre

    Il suffit ensuite de créer l’animation autour de chaque carte et d’ajouter whileHover et un whileTap au composant motion pour obtenir l’effet de rebond au hover et d’opacité au clic :

    //index.tsx
    const homePageWrapperVariant = {
     hidden: { opacity: 0, y: '-100px' },
     visible: {
       opacity: 1,
       y: 0,
       transition: { duration: 1.5, when: 'beforeChildren', staggerChildren: 1 },
     },
    }
    
    const chapterCardVariant = {
     hidden: {
       opacity: 0,
     },
     visible: {
       opacity: 1,
       transition: {
         duration: 1.5,
       },
     },
     hover: {
       scale: 1.1,
       transition: { type: 'spring', bounce: 0.6 },
     },
     tap: {
       opacity: 0.4,
     },
    }
    
    export default function AliceFramerMotion() {
     return (
       <Center height="100vh" flexDirection="column">
         <Head>
           <title>Alice Framer Motion App</title>
           <link rel="icon" href="/favicon.ico" />
         </Head>
         <motion.div
           variants={homePageWrapperVariant}
           initial="hidden"
           animate="visible"
         >
           <Text textAlign="center" fontSize="3xl" p={4}>
             {book.title}
           </Text>
           <Flex paddingY={10}>
             {book.chapters.map((chapter, index) => (
               <Box p={4} key={index}>
                 <motion.div whileHover="hover" variants={chapterCardVariant}>
                   <Link href={`/chapter/${index + 1}`}>
                     <Square
    

    Le résultat sur l'animation :

    7. AnimatePresence : Créer des animations de sortie

    Une autre fonctionnalité intéressante de Framer Motion permet d’effectuer une animation spécifique au moment où les composants sont supprimés du DOM. On utilise pour ça un nouveau composant <AnimatePresence>. On peut alors ajouter à notre composant motion une prop “exit” et spécifier dans la variant un comportement d’animation pour notre composant avant sa suppression.

    Voyons cela en pratique avec une dernière animation, DrinkMe.tsx. Il s’agit ici d’exprimer l’explosion de saveurs de la potion décrite par Alice. Nous voulons donc effectuer les animations suivantes :

    • au chargement du composant une bouteille apparaît
    • au clic sur buvez moi le composant de la bouteille est supprimé
    • avant qu’il soit supprimé on lance les animations de sortie de ses enfants, les icônes d’aliments, qui devront partir dans des directions différentes
    • on lance enfin l’animation de sortie de la bouteille
    • le bouton permet de relancer l’animation

    On commence par définir la variant pour la bouteille:

    • exit représente notre animation de sortie pour la bouteille
    • staggerChildren précise le timing d'échelonnement pour les animations de sortie de chaque enfant
    • when:”afterChildren” nous assure que l’animation de sortie de la bouteille ne sera pas lancée avant les enfants
    //DrinkMe.tsx
    const bottleVariant = {
      hidden: {
        opacity: 0,
        y: 0,
      },
      visible: {
        opacity: 1,
        transition: {
          duration: 1,
        },
      },
      exit: {
        y: ['-30px', '100px'],
        opacity: 0,
        transition: {
          duration: 1,
          when: 'afterChildren',
          staggerChildren: 0.8,
        },
      },
    }
    

    Les variants dynamiques

    On passe ensuite à la variant pour les enfants. Sachant que chaque icône devra partir dans un sens différent, on tire profit de la possibilité de passer dynamiquement une valeur aux animations par le composant motion (ici la direction d’animation pour x) :

    const foodVariant = {
      visible: {
        opacity: 0,
        y: -50,
        position: 'absolute',
      },
      exit: (xDirection) => ({
        opacity: [1, 0],
        y: -200,
        x: xDirection,
        rotate: 360,
        transition: {
          duration: 2,
        },
      }),
    }
    

    On peut maintenant définir nos composants motion et ajouter AnimatePresence :

    • chaque motion.div représentant une icône de nourriture possède alors une prop custom par laquelle nous pouvons passer la direction x à appliquer à l’animation de sortie sur notre variant
    • AnimatePresence dispose d’un callback nous permettant de désactiver le bouton pour relancer l’animation tant que toutes les animations de sortie n’ont pas été jouées
    <AnimatePresence onExitComplete={() => setDisabledBtn(false)}>
      {!isPotionEmpty && (
        <motion.div variants={bottleVariant} animate="visible" initial="hidden" exit="exit">
          <motion.div variants={foodVariant} initial={false} custom={-50}>
            <Image src="/alice/candy.png" h="80px" />
          </motion.div>
          <motion.div variants={foodVariant} initial={false} custom={100}>
            <Image src="/alice/pineapple.png" h="80px" />
          </motion.div>
          <motion.div variants={foodVariant} initial={false} custom={-100}>
            <Image src="/alice/dinde.png" h="80px" />
          </motion.div>
          <motion.div variants={foodVariant} initial={false} custom={50}>
            <Image src="/alice/cake-slice.png" h="80px" />
          </motion.div>
          <Image src="/alice/potion.png" h="100px" />
        </motion.div>
      )}
    </AnimatePresence>
    

    On obtient le résultat suivant :

    MIAM !

    Voilà vous avez maintenant un premier aperçu des capacités de Framer Motion qui est un outil de gestion d’animation puissant pour vos composants. Ce premier tour ne couvre qu’une partie des fonctionnalités de l’API, car vous l’aurez compris les possibilités sont infinies.

    J’espère que vous avez passé un bon moment à donner vie à vos composants, et que ça vous a donné envie d’en savoir plus sur cette super librairie. Nous verrons dans un prochain article d’autres fonctionnalités intéressantes comme la gestion du scroll, la détection du drag des éléments, les paths animés, ou les animations coordonnées de layout avec AnimateSharedLayout.

    Encore tout un programme à découvrir!

    À découvrir également

    Premier Octet vous accompagne dans le développement de vos projets avec react

    Discuter de votre projet react