📅 Publié le 15 décembre 2024 • ⏱️ 12 min de lecture

Guide Complet Framer Motion pour React

Découvrez comment créer des animations web fluides et performantes avec Framer Motion. De l'installation aux techniques avancées, ce guide vous donnera toutes les clés pour maîtriser cette bibliothèque d'animation incontournable.

Framer MotionReactAnimationPerformanceTutorial

🚀 Introduction à Framer Motion

Framer Motion est une bibliothèque d'animation pour React qui simplifie la création d'animations fluides et performantes. Développée par l'équipe Framer, elle offre une API intuitive qui permet de créer des interfaces utilisateur dynamiques sans sacrifier les performances.

Pourquoi choisir Framer Motion ?

  • API déclarative : Définissez vos animations directement dans le JSX
  • Performances optimisées : Utilise le GPU pour des animations fluides
  • Gestes tactiles : Support natif du drag & drop et des gestes
  • AnimatePresence : Gérez facilement les animations d'entrée/sortie
  • Écosystème React : Intégration parfaite avec les hooks et le state

💡 Le saviez-vous ?

Framer Motion est utilisé par des entreprises comme Stripe, Coinbase, et bien sûr Framer. Sa popularité vient de sa capacité à créer des animations complexes avec un code minimal.

📦 Installation et configuration

Installation via npm ou yarn

# Avec npm
npm install framer-motion

# Avec yarn  
yarn add framer-motion

# Avec pnpm
pnpm add framer-motion

Configuration avec Next.js

Si vous utilisez Next.js (comme nous le recommandons chez Atypik Code), vous pouvez utiliser Framer Motion directement sans configuration supplémentaire.

// components/AnimatedComponent.jsx
"use client" // Important pour Next.js 13+

import { motion } from 'framer-motion'

export default function AnimatedComponent() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    >
      Hello Framer Motion !
    </motion.div>
  )
}

🎨 Animations de base avec motion

Le composant motion

Tous les éléments HTML peuvent être animés en les préfixant avec motion. :

import { motion } from 'framer-motion'

// Div animée
<motion.div />

// Bouton animé  
<motion.button />

// Image animée
<motion.img />

// SVG animé
<motion.svg />

Propriétés fondamentales

  • initial : État initial de l'animation
  • animate : État final de l'animation
  • transition : Configuration de la transition
  • exit : Animation de sortie (avec AnimatePresence)

Exemple : Bouton avec hover

function AnimatedButton() {
  return (
    <motion.button
      className="px-6 py-3 bg-purple-600 text-white rounded-lg"
      whileHover={{ 
        scale: 1.05,
        boxShadow: "0 10px 25px rgba(139, 92, 246, 0.3)"
      }}
      whileTap={{ scale: 0.95 }}
      transition={{ type: "spring", stiffness: 300 }}
    >
      Cliquez-moi !
    </motion.button>
  )
}

🎭 AnimatePresence : gérer les animations d'entrée/sortie

AnimatePresence permet d'animer les composants qui apparaissent et disparaissent du DOM. C'est essentiel pour les modales, les notifications, ou tout contenu conditionnel.

Exemple : Modal animée

import { motion, AnimatePresence } from 'framer-motion'
import { useState } from 'react'

function AnimatedModal() {
  const [isOpen, setIsOpen] = useState(false)
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Ouvrir la modal
      </button>
      
      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 bg-black/50 flex items-center justify-center"
            onClick={() => setIsOpen(false)}
          >
            <motion.div
              initial={{ scale: 0.8, opacity: 0 }}
              animate={{ scale: 1, opacity: 1 }}
              exit={{ scale: 0.8, opacity: 0 }}
              transition={{ type: "spring", damping: 20 }}
              className="bg-white p-8 rounded-xl"
              onClick={(e) => e.stopPropagation()}
            >
              <h2>Modal animée</h2>
              <p>Contenu de la modal avec Framer Motion</p>
              <button onClick={() => setIsOpen(false)}>
                Fermer
              </button>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </>
  )
}

⚠️ Point important

AnimatePresence doit envelopper les éléments conditionnels, pas l'élément animé lui-même. Les enfants directs doivent avoir une prop key unique.

🎯 Hooks avancés et contrôle d'animations

useAnimation() : Contrôle programmatique

import { motion, useAnimation } from 'framer-motion'
import { useEffect } from 'react'

function ControlledAnimation() {
  const controls = useAnimation()
  
  useEffect(() => {
    // Séquence d'animations
    const sequence = async () => {
      await controls.start({ x: 100 })
      await controls.start({ y: 100 })
      await controls.start({ x: 0, y: 0 })
    }
    
    sequence()
  }, [controls])
  
  return (
    <motion.div
      animate={controls}
      className="w-20 h-20 bg-purple-500 rounded-lg"
    />
  )
}

useInView() : Animations au scroll

import { motion, useInView } from 'framer-motion'
import { useRef } from 'react'

function ScrollAnimation() {
  const ref = useRef(null)
  const isInView = useInView(ref, { once: true })
  
  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
      transition={{ duration: 0.8 }}
      className="p-8 bg-gray-800 rounded-xl"
    >
      <h3>Animation au scroll</h3>
      <p>Ce contenu s'anime quand il devient visible</p>
    </motion.div>
  )
}

🎨 Animations de path SVG

Une des fonctionnalités les plus impressionnantes de Framer Motion est la capacité d'animer les chemins SVG avec pathLength.

function AnimatedSVG() {
  return (
    <svg width="200" height="200" viewBox="0 0 200 200">
      <motion.circle
        cx="100"
        cy="100"
        r="80"
        stroke="#8B5CF6"
        strokeWidth="4"
        fill="transparent"
        initial={{ pathLength: 0 }}
        animate={{ pathLength: 1 }}
        transition={{ duration: 2, ease: "easeInOut" }}
      />
    </svg>
  )
}

Exemple avancé : Logo animé

function AnimatedLogo() {
  const draw = {
    hidden: { pathLength: 0, opacity: 0 },
    visible: (i) => {
      const delay = 1 + i * 0.5
      return {
        pathLength: 1,
        opacity: 1,
        transition: {
          pathLength: { delay, type: "spring", duration: 1.5, bounce: 0 },
          opacity: { delay, duration: 0.01 }
        }
      }
    }
  }

  return (
    <motion.svg
      width="200"
      height="200"
      viewBox="0 0 200 200"
      initial="hidden"
      animate="visible"
    >
      <motion.path
        d="M 50 100 L 150 100"
        stroke="#8B5CF6"
        strokeWidth="4"
        variants={draw}
        custom={0}
      />
      <motion.path
        d="M 100 50 L 100 150"
        stroke="#8B5CF6"
        strokeWidth="4"
        variants={draw}
        custom={1}
      />
    </motion.svg>
  )
}

⚡ Optimisation des performances

Propriétés GPU-accélérées

Pour des performances optimales, privilégiez ces propriétés :

  • x, y au lieu de left, top
  • scale au lieu de width, height
  • rotate au lieu de transformations CSS
  • opacity pour les fondus

Layout animations avec layoutId

function SharedLayoutAnimation() {
  const [isExpanded, setIsExpanded] = useState(false)
  
  return (
    <motion.div
      layout
      layoutId="expandable-card"
      className={isExpanded ? "large-card" : "small-card"}
      onClick={() => setIsExpanded(!isExpanded)}
    >
      <motion.h2 layout="position">Titre</motion.h2>
      {isExpanded && (
        <motion.p
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          Contenu additionnel
        </motion.p>
      )}
    </motion.div>
  )
}

🚀 Astuce performance

Utilisez will-change: transform en CSS pour préparer le GPU, et transform3d(0,0,0) pour forcer l'accélération matérielle sur les anciens navigateurs.

💼 Cas d'usage concrets

1. Menu hamburger animé

function AnimatedHamburger({ isOpen, toggle }) {
  const topVariants = {
    closed: { rotate: 0, y: 0 },
    open: { rotate: 45, y: 8 }
  }
  
  const centerVariants = {
    closed: { opacity: 1 },
    open: { opacity: 0 }
  }
  
  const bottomVariants = {
    closed: { rotate: 0, y: 0 },
    open: { rotate: -45, y: -8 }
  }
  
  return (
    <button onClick={toggle} className="flex flex-col w-8 h-8 justify-center">
      <motion.span
        className="w-8 h-1 bg-white mb-1"
        variants={topVariants}
        animate={isOpen ? "open" : "closed"}
      />
      <motion.span
        className="w-8 h-1 bg-white mb-1"
        variants={centerVariants}
        animate={isOpen ? "open" : "closed"}
      />
      <motion.span
        className="w-8 h-1 bg-white"
        variants={bottomVariants}
        animate={isOpen ? "open" : "closed"}
      />
    </button>
  )
}

2. Carrousel d'images fluide

function ImageCarousel({ images }) {
  const [current, setCurrent] = useState(0)
  
  return (
    <div className="relative overflow-hidden w-full h-96">
      <AnimatePresence mode="wait">
        <motion.img
          key={current}
          src={images[current]}
          initial={{ x: 300, opacity: 0 }}
          animate={{ x: 0, opacity: 1 }}
          exit={{ x: -300, opacity: 0 }}
          transition={{ type: "spring", damping: 20 }}
          className="absolute inset-0 w-full h-full object-cover"
        />
      </AnimatePresence>
      
      <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2">
        {images.map((_, index) => (
          <button
            key={index}
            onClick={() => setCurrent(index)}
            className={`w-3 h-3 mx-1 rounded-full ${
              current === index ? 'bg-white' : 'bg-white/50'
            }`}
          />
        ))}
      </div>
    </div>
  )
}

✅ Meilleures pratiques

1. Gérez l'accessibilité

// Respectez les préférences utilisateur
const prefersReducedMotion = useReducedMotion()

<motion.div
  animate={{ 
    x: prefersReducedMotion ? 0 : 100 
  }}
  transition={{ 
    duration: prefersReducedMotion ? 0 : 0.5 
  }}
/>

2. Utilisez des variants pour la réutilisabilité

const fadeInUp = {
  hidden: { opacity: 0, y: 20 },
  visible: { 
    opacity: 1, 
    y: 0,
    transition: { duration: 0.6 }
  }
}

// Réutilisable sur plusieurs composants
<motion.div variants={fadeInUp} initial="hidden" animate="visible">
  Contenu animé
</motion.div>

3. Optimisez les re-renders

  • Utilisez useMemo pour les objets de transition complexes
  • Évitez les animations inline dans le render
  • Préférez les variants aux objets inline
  • Utilisez layout avec parcimonie

🎨 Notre approche chez Atypik Code

Nous intégrons Framer Motion dans tous nos projets Next.js pour créer des expériences utilisateur mémorables. L'animation n'est pas juste décorative, elle guide l'utilisateur et améliore l'UX.

🎓 Conclusion

Framer Motion est un outil puissant qui transforme la façon dont nous créons des interfaces React. Avec une API intuitive et des performances optimisées, il permet de créer des animations de qualité professionnelle sans complexité excessive.

Les points clés à retenir :

  • Commencez simple avec les props initial, animate et transition
  • Utilisez AnimatePresence pour les animations d'entrée/sortie
  • Exploitez les hooks comme useInView et useAnimation pour des cas avancés
  • Optimisez les performances avec les propriétés GPU-accélérées
  • Respectez l'accessibilité avec useReducedMotion

Chez Atypik Code, nous utilisons Framer Motion pour tous nos projets web en Haute-Savoie, créant des expériences utilisateur modernes et engageantes.

Besoin d'un site avec des animations professionnelles ?

Nous créons des sites web modernes avec Framer Motion pour les entreprises en Haute-Savoie. Animations fluides, performances optimisées et design sur mesure.