Développement

Pourquoi tes animations React rament à 30 fps — et le réflexe Framer Motion qui corrige ça

Une animation qui saccade vient presque toujours d'une propriété mal choisie. Le réflexe qui corrige ça, et les patterns Motion (ex-Framer Motion v12) pour des interfaces fluides à 60 fps.

10 oct 20259 min de lecturePASCAL POTVIN
Écouter l'article

Pourquoi ça rame à 30 fps

Quand une animation React saccade, la cause est presque toujours la même, et elle n'a rien à voir avec la puissance de la machine : on anime la mauvaise propriété. Animer width, height, top, left, margin ou padding force le navigateur à recalculer la mise en page — un reflow — à chaque frame, et c'est exactement ce qui fait tomber une animation de 60 à 30 images par seconde. Le réflexe qui corrige ça tient en une phrase : n'anime que transform et opacity. Ce sont les deux seules propriétés composées par le GPU, qui ne déclenchent aucun reflow.

C'est précisément la valeur par défaut de Motion — le nouveau nom de Framer Motion, disponible sur motion.dev, désormais en version 12 et importé depuis motion/react. La bibliothèque anime transform et opacity par défaut, ce qui te place du bon côté de la performance sans y penser. Mais elle offre aussi des fonctionnalités — les layout animations notamment — qui peuvent te ramener dans le piège du reflow si tu ne sais pas comment elles fonctionnent. Voyons les patterns qui produisent des interfaces vivantes sans sacrifier les 60 fps.

Les fondamentaux : motion components, variants, AnimatePresence

Le point d'entrée est le motion component : motion.div, motion.button transforment un élément en élément animable. Les props initial, animate et exit définissent l'état de départ, l'état cible et l'état de sortie. C'est une API déclarative — on décrit des états, pas des séquences — naturelle pour un designer qui pense en termes visuels.

jsx
import { motion, AnimatePresence } from "motion/react";

function Toast({ visible, message }) {
  return (
    <AnimatePresence>
      {visible && (
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: 20 }}
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
        >
          {message}
        </motion.div>
      )}
    </AnimatePresence>
  );
}

Les variants rendent les animations complexes gérables : on nomme des états — hidden, visible, hover — dans un objet séparé, et ils se propagent aux enfants, avec staggerChildren pour échelonner une cascade sans logique de timing manuelle. Je centralise les variants du projet dans un fichier dédié, ce qui crée un vocabulaire de mouvement cohérent. Et AnimatePresence règle le angle mort de React : sans lui, un composant retiré du DOM disparaît d'un coup ; avec lui, l'animation exit joue avant le retrait — indispensable pour modales, notifications et transitions.

Layout animations et shared element transitions

C'est la fonctionnalité la plus spectaculaire — et la plus piégeuse côté perf. En ajoutant la prop layout, Motion anime automatiquement tout changement de position ou de taille via la technique FLIP. Un filtre qui réordonne une grille, un panneau qui repousse le contenu : tout devient fluide. La prop layoutId va plus loin avec les shared element transitions : même layoutId sur une miniature et sur l'image de détail, et Motion anime la transformation de l'une à l'autre — ce genre de transition d'app native qui était un cauchemar à coder sur le web. LayoutGroup coordonne les recalculs entre animations simultanées pour éviter les conflits.

Le piège : contrairement à transform/opacity, une layout animation lit le DOM pour calculer les changements de position. Avec beaucoup d'éléments animés en même temps, ça peut faire saccader. J'utilise donc layout avec parcimonie et je regroupe les éléments concernés dans un LayoutGroup. C'est exactement le genre de fonctionnalité magnifique en démo qui rame en production si on l'applique partout.

Spring physics, gestes et scroll

Motion utilise par défaut des ressorts physiques plutôt que des courbes de Bézier. Les paramètres stiffness, damping et mass simulent un comportement que le cerveau perçoit comme naturel — masse faible et amortissement élevé pour une micro-interaction nette, masse élevée et amortissement faible pour un rebond ludique. Ces paramètres physiques sont plus intuitifs que des durées en millisecondes. Le système de gestes — whileHover, whileTap, whileDrag, whileInView — crée des interactions tactiles en déclaratif : un carrousel draggable avec inertie tient en quelques lignes. Le hook useScroll expose scrollYProgress pour les barres de progression, parallaxes et révélations. Et pour le SVG, pathLength permet l'animation de tracé progressif, parfaite pour les icônes animées et les loaders.

CSS natif, Motion ou GSAP : quand utiliser quoi

Tout n'a pas besoin de Motion. Voici comment je tranche.

BesoinOutilPourquoi
Hover, transitions simples, entréesCSS natif (@starting-style)meilleure perf, zéro JS
Transition de page (même app)View Transitions APInatif, Baseline depuis oct. 2025
Layout, gestes, orchestrationMotionratio effort/résultat imbattable
Timeline cinématographique préciseGSAPcontrôle maximal

La View Transitions API mérite une mention : les transitions same-document sont passées Baseline en octobre 2025, et les transitions cross-document (multi-pages) fonctionnent dans Chrome et Edge 126+ et Safari 18.2+, Firefox étant encore en cours — donc sur ces moteurs, la transition est simplement ignorée sans casse. Pour une navigation simple, cette API native peut suffire et t'évite d'embarquer une bibliothèque. Dans la pratique, je combine souvent les trois approches dans un même projet, chacune là où elle excelle.

Performance et accessibilité en production

Tu l'as compris : la règle de perf tient à animer transform et opacity, et à traiter les layout animations comme un outil de précision, pas un réflexe — layout avec parcimonie, regroupé dans un LayoutGroup. L'autre exigence non négociable est l'accessibilité. Le hook useReducedMotion lit la préférence système prefers-reduced-motion ; je l'intègre systématiquement pour désactiver ou simplifier les animations chez les personnes sensibles au mouvement. Un bouton qui revient à une transition instantanée reste parfaitement fonctionnel.

Ma règle d'or reste fonctionnelle : si un mouvement ne rend pas l'interface plus compréhensible ou plus agréable, il n'a pas sa place. Chaque animation doit guider l'attention, signaler un changement d'état, créer une continuité spatiale ou donner un feedback. Une animation qui n'existe que pour être remarquée est une animation de trop. À 60 fps comme à 30, la retenue est le vrai signe de maîtrise.

§ COMMENTAIRES

Laisser un commentaire