Skip to content
Learni
Voir tous les tutoriels
Développement Frontend

Comment maîtriser Framer Motion avancé en 2026

Read in English

Introduction

Framer Motion est la bibliothèque d'animations la plus puissante pour React en 2026, surpassant les APIs CSS natives grâce à sa déclaration simple et ses performances GPU-accélérées. Pour les développeurs avancés, elle excelle dans les cas complexes : animations de layout responsives, gestes tactiles fluides, transitions scroll-linked et orchestrations d'équipes d'éléments.

Pourquoi l'adopter ? Les animations ne sont plus des gadgets : elles guident l'utilisateur, améliorent l'accessibilité (via useReducedMotion) et boostent les métriques Core Web Vitals comme CLS (Cumulative Layout Shift). Ce tutoriel avancé vous guide pas à pas, du setup à des implémentations production-ready, avec du code TypeScript complet et fonctionnel. À la fin, vous créerez des UIs qui rivalisent avec celles de Figma ou Notion. Prêt à transformer vos composants statiques en expériences immersives ? (142 mots)

Prérequis

  • Node.js 20+ et npm/yarn/pnpm
  • React 18+ avec Vite ou Next.js
  • Connaissances avancées en TypeScript et hooks React
  • Un éditeur comme VS Code avec extension Framer Motion (optionnel)
  • CodeSandbox ou StackBlitz pour tester instantanément

Setup du projet et installation

terminal
npm create vite@latest framer-motion-advanced -- --template react-ts
cd framer-motion-advanced
npm install
npm install framer-motion@latest
npm run dev

Ce script initialise un projet Vite React + TypeScript, installe Framer Motion (version 11+ en 2026) et lance le serveur dev. Vite assure un HMR ultra-rapide pour itérer sur les animations sans rebuild complet. Évitez Create React App : trop lent pour les previews animées.

Les motion components avancés

Les motion.* remplacent les div, span etc., en ajoutant des props comme animate, whileHover. Pour l'avancé, focus sur layout pour animer les changements de taille/position sans JavaScript custom, évitant les layout thrashing.

Animations de layout basiques

src/App.tsx
import { useState } from 'react';
import { motion } from 'framer-motion';

function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div style={{ padding: '2rem' }}>
      <motion.button
        onClick={() => setIsOpen(!isOpen)}
        style={{ marginBottom: '1rem' }}
      >
        Toggle
      </motion.button>
      <motion.div
        layout
        style={{
          width: isOpen ? 400 : 200,
          height: 200,
          backgroundColor: '#ff6b6b',
          borderRadius: 8
        }}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ duration: 0.3 }}
      />
    </div>
  );
}

export default App;

import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

Ce composant démontre layout : l'élément s'étend fluidement sans saut. initial et animate gèrent l'entrée. Piège : sans layout, les changements DOM cassent l'animation – toujours l'ajouter pour responsive. Copiez-collez dans Vite pour tester.

Variants et transitions orchestrées

Les variants centralisent les états (open/closed), comme un chef d'orchestre. transition affine easing, delay et stagger pour des effets naturels. Analogy : un menu hamburger où les lignes stagger vers le X.

Menu avec variants et stagger

src/App.tsx
import { useState } from 'react';
import { motion } from 'framer-motion';

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1
    }
  }
};

const itemVariants = {
  hidden: { y: -20, opacity: 0 },
  visible: {
    y: 0,
    opacity: 1,
    transition: { duration: 0.3, ease: 'easeOut' }
  }
};

function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div style={{ padding: '2rem' }}>
      <motion.button
        onClick={() => setIsOpen(!isOpen)}
        style={{ marginBottom: '1rem' }}
        whileHover={{ scale: 1.05 }}
        whileTap={{ scale: 0.95 }}
      >
        Menu
      </motion.button>
      <motion.ul
        layout
        variants={containerVariants}
        initial="hidden"
        animate={isOpen ? 'visible' : 'hidden'}
        style={{ listStyle: 'none', padding: 0 }}
      >
        <motion.li variants={itemVariants}>Item 1</motion.li>
        <motion.li variants={itemVariants}>Item 2</motion.li>
        <motion.li variants={itemVariants}>Item 3</motion.li>
      </motion.ul>
    </div>
  );
}

export default App;

import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

staggerChildren orchestre les enfants comme un ballet. whileHover/tap ajoute du feedback tactile. Piège : oublier custom pour passer des props aux enfants – ici implicite via variants. Parfait pour modals ou accordéons.

Gestures avancées avec drag

src/App.tsx
import { motion } from 'framer-motion';

function App() {
  return (
    <div style={{ padding: '2rem', height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <motion.div
        drag
        dragConstraints={{ top: -50, left: -50, right: 50, bottom: 50 }}
        dragElastic={0.2}
        whileDrag={{ scale: 1.1, rotate: 5 }}
        style={{
          width: 100,
          height: 100,
          backgroundColor: '#4ecdc4',
          borderRadius: 12,
          cursor: 'grab'
        }}
      />
    </div>
  );
}

export default App;

import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

drag active le drag natif multiplateforme (touch/mouse). dragConstraints limite à un viewport, dragElastic ajoute rebond. whileDrag anime pendant l'interaction. Piège : sans cursor: grab, UX confuse – toujours ajouter pour mobile-first.

Animations liées au scroll

Avec useScroll et useTransform, liez des propriétés CSS au scroll comme Parallax pro. Idéal pour hero sections ou timelines : l'opacité fade-in au scroll, sans IntersectionObserver verbeux.

Parallax scroll-triggered

src/App.tsx
import { useScroll, useTransform } from 'framer-motion';
import { useRef } from 'react';
import { motion } from 'framer-motion';

function App() {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end start']
  });
  const y = useTransform(scrollYProgress, [0, 1], [0, -200]);

  return (
    <div style={{ height: '200vh' }}>
      <section style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <h1 style={{ fontSize: '3rem' }}>Scroll down!</h1>
      </section>
      <motion.section
        ref={ref}
        style={{
          height: '100vh',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          y
        }}
      >
        <motion.div
          style={{
            width: 300,
            height: 300,
            backgroundColor: '#45b7d1',
            borderRadius: 20
          }}
          animate={{ opacity: scrollYProgress }}
        />
      </motion.section>
    </div>
  );
}

export default App;

import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

useScroll tracke le progrès, useTransform mappe [0,1] vers des valeurs (ex: y parallax). offset définit quand démarrer. Piège : oublier ref sur target – animation ne trigger pas. Optimisé pour 60fps même sur mobile.

AnimatePresence avec exits

src/App.tsx
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';

function App() {
  const [items, setItems] = useState(['1', '2', '3']);

  const handleAdd = () => setItems((prev) => [...prev, `${prev.length + 1}`]);
  const handleRemove = (index: number) => setItems((prev) => prev.filter((_, i) => i !== index));

  return (
    <div style={{ padding: '2rem' }}>
      <button onClick={handleAdd}>Add</button>
      <AnimatePresence>
        {items.map((item, index) => (
          <motion.div
            key={item}
            initial={{ opacity: 0, x: 50 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: -50, scale: 0.8 }}
            transition={{ duration: 0.3 }}
            style={{
              padding: '1rem',
              margin: '0.5rem 0',
              backgroundColor: '#f0932b',
              borderRadius: 8
            }}
            onClick={() => handleRemove(index)}
          >
            Item {item}
          </motion.div>
        ))}
      </AnimatePresence>
    </div>
  );
}

export default App;

import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

AnimatePresence anime les exits lors du unmount, essentiel pour listes dynamiques. key unique trigger l'animation. Piège : sans AnimatePresence, exits instantanés – casse l'UX. Utilisez pour toasts ou carrousels.

Bonnes pratiques

  • Toujours utiliser layoutId pour shared layouts entre routes (Next.js).
  • Respectez prefers-reduced-motion via pour accessibilité.
  • Bundleisez avec framer-motion/layout pour tree-shaking.
  • Testez sur mobile : dragPropagation={false} évite conflits.
  • Profilez avec React DevTools Profiler pour détecter re-renders excessifs.

Erreurs courantes à éviter

  • Oublier layout : provoque CLS pénalisant SEO.
  • animate inline sans variants : code répétitif et dur à maintenir.
  • Ignorer mode="wait" dans AnimatePresence : overlaps chaotiques.
  • useTransform sans offset précis : animations imprévisibles au scroll.

Pour aller plus loin