Créer une section chiffres clés animée en React
Une section statistiques animée en React utilise whileInView de Framer Motion pour déclencher un reveal décalé (fade + slide) sur chaque carte au scroll. Chaque carte s'anime indépendamment avec un offset de 100ms pour créer un effet cascade.
- Stack : React + Framer Motion + Tailwind v4, ~88 lignes, zéro dépendance supplémentaire.
- Animation : whileInView avec viewport once:true, délai décalé de 100ms par carte, easing spring personnalisé [0.16, 1, 0.3, 1].
- Mise en page : CSS Grid split, bloc texte à gauche, grille 2×2 de cartes à droite à partir du breakpoint lg.
- Entièrement thémisé via des propriétés CSS personnalisées ; aucune couleur codée en dur.
- Accessible : section/h2 sémantiques, valeurs des stats en texte brut (compatible lecteur d'écran), sans artifices ARIA.
About Numbers est une section React qui associe un bloc narratif court à une grille 2×2 de grandes cartes statistiques en couleur d'accent. Chaque carte s'anime à l'entrée dans le viewport, décalée dans le temps pour que la grille paraisse vivante plutôt que statique. C'est le pattern le plus courant sur les sites SaaS et agences : des chiffres qui construisent la crédibilité en un coup d'œil.
Anatomie
La section extérieure utilise des propriétés CSS personnalisées pour le padding et la largeur max, héritant ainsi des tokens d'espacement du projet. À l'intérieur, un CSS Grid unique se sépare en deux colonnes au breakpoint lg : une colonne gauche avec le titre h2 et un court paragraphe de description, et une colonne droite qui est elle-même une grille 2 colonnes de cartes statistiques. Chaque carte est un conteneur arrondi avec un fond background-alt, une bordure fine, la valeur numérique en grand texte accent, et le label en petites lettres majuscules espacées.
Comment ça marche
Le bloc texte est enveloppé dans un motion.div unique avec opacity 0 et y:16 en initial, déclenché par whileInView. Les cartes statistiques reçoivent le même traitement mais avec un délai par index de i * 0.1 seconde, créant une cascade naturelle de gauche à droite et de haut en bas. Toutes les transitions partagent un easing cubic-bezier personnalisé [0.16, 1, 0.3, 1], une courbe fast-out qui paraît vive sans être brusque. L'option viewport once:true empêche la ré-animation au scroll vers le haut, gardant l'expérience propre.
Comment le coder en React
Mettre en place le layout grid split
Enveloppe tout dans une section qui lit le padding et la largeur max depuis les tokens CSS. À l'intérieur, utilise un CSS Grid avec grid-cols-1 sur mobile et lg:grid-cols-2 sur desktop, avec items-center pour aligner les deux moitiés verticalement. C'est la coquille structurelle.
<section style={{ padding: "var(--section-padding-y)", background: "var(--color-background)" }}> <div className="mx-auto" style={{ maxWidth: "var(--container-max-width)" }}> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"> {/* text block */} {/* stats grid */} </div> </div> </section>Animer le bloc texte au scroll
Enveloppe le titre et le paragraphe dans un motion.div de Framer Motion. Définis initial à opacity:0 et y:16, puis whileInView à opacity:1 et y:0. Passe viewport={{ once: true }} pour que l'animation ne se déclenche qu'une fois. Utilise la constante EASE personnalisée pour un rendu vif.
const EASE = [0.16, 1, 0.3, 1] as const; <motion.div initial={{ opacity: 0, y: 16 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, ease: EASE }} > <h2>{title}</h2> <p>{description}</p> </motion.div>Construire les cartes statistiques en cascade
Itère sur le tableau stats, en enveloppant chaque carte dans son propre motion.div. Passe delay: i * 0.1 dans la transition pour que les cartes entrent l'une après l'autre. Chaque carte lit le border-radius, l'arrière-plan et la couleur de bordure depuis les tokens CSS. La valeur s'affiche dans un span en couleur accent, et le label dans un span muted plus petit.
{stats.map((stat, i) => ( <motion.div key={stat.label} initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.45, delay: i * 0.1, ease: EASE }} style={{ background: "var(--color-background-alt)", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", }} > <span style={{ color: "var(--color-accent)" }}>{stat.value}</span> <span style={{ color: "var(--color-foreground-muted)" }}>{stat.label}</span> </motion.div> ))}Fournir les données statistiques
Chaque stat est un objet avec une chaîne value et une chaîne label. Les valeurs sont des chaînes pré-formatées comme "12k+" ou "99%", pas de calcul numérique au runtime. Passe-les via la prop stats ou remplace le mock par défaut par tes vraies données au point d'appel.
const stats = [ { value: "12k+", label: "Clients" }, { value: "99%", label: "Satisfaction" }, { value: "8 ans", label: "Expérience" }, { value: "40+", label: "Pays" }, ]; <AboutNumbers title="Nos chiffres" description="..." stats={stats} />
Quand l'utiliser
Utilise cette section en milieu de page sur une landing d'entreprise, un site marketing SaaS ou un portfolio d'agence où tu dois établir la confiance par les chiffres avant le CTA. Elle fonctionne mieux avec 4 à 6 stats de poids visuel similaire. Évite-la quand tes chiffres ne sont pas encore assez convaincants (produits en phase précoce) ou quand la page est déjà chargée en tableaux de données et graphiques, ajouter un autre bloc de chiffres crée du bruit.
Utilisé par
- Stripe, Utilise de grands chiffres en gras dans des layouts split sur ses pages produit pour mettre en avant des métriques de volume et de fiabilité.
- Notion, Présente une section à propos centrée sur les chiffres sur sa page entreprise avec des révélations de cartes décalées.
- Intercom, Utilise des blocs statistiques en grille en milieu de page pour construire la crédibilité avant la section tarifaire.
- Webflow, Associe un court récit de marque à une grille de cartes de métriques plateforme sur ses pages d'accueil et à propos.
FAQ
Comment ajouter une animation de compteur (count-up) ?
L'implémentation actuelle affiche des chaînes pré-formatées, ce qui garde le composant simple et évite les re-rendus. Pour ajouter un compteur, remplace le span de valeur par un composant qui utilise useMotionValue de Framer Motion et l'anime de 0 vers le chiffre cible dans un useEffect déclenché par un IntersectionObserver ou le callback whileInView.
Puis-je avoir plus de 4 cartes statistiques ?
La grille est en grid-cols-2 sans nombre de lignes fixé, donc elle s'adapte à n'importe quel nombre pair d'éléments. Un nombre impair laissera un vide en dernière ligne. Avec 6 cartes, le layout fonctionne bien. Au-delà de 8, envisage un défilement horizontal ou une grille 3 colonnes sur les grands écrans.
Pourquoi utiliser whileInView plutôt que useEffect + IntersectionObserver ?
whileInView est un wrapper léger autour d'IntersectionObserver intégré dans Framer Motion. Comme le composant dépend déjà de Framer Motion pour l'animation, utiliser whileInView évite une configuration d'observer séparée et garde le code plus court. Il n'y a aucune différence de performance.
Comment changer la couleur accent des valeurs statistiques ?
Les valeurs utilisent `color: var(--color-accent)` qui hérite du preset de thème actif. Change l'attribut data-theme sur un élément parent pour modifier toute la palette, ou surcharge la variable localement avec un style inline sur la section si tu as besoin d'une couleur ponctuelle.