Retour au catalogue

Stats Slot Machine

Les chiffres des statistiques défilent comme des colonnes de slot machine au scroll. Chaque digit individuel est une colonne 0-9 animée avec spring physics (rebond final satisfaisant). Stagger de droite à gauche pour un effet cascade élégant.

statscomplex Both Responsive a11y
boldelegantcorporatesaasagencyuniversalgridcentered
Theme

Créer une section stats avec animation machine à sous en React

Un compteur stats machine à sous en React affiche chaque chiffre comme une colonne indépendante de 0 à 9, animée avec un spring Framer Motion. Au scroll, chaque colonne rebondit vers son chiffre cible avec un stagger de droite à gauche, les unités se stabilisent en premier et l'effet ressemble à une vraie machine à sous qui se résout.

  • Stack : React 18 + Framer Motion 11, ~308 lignes, zéro dépendance supplémentaire au-delà de framer-motion.
  • API clé : useSpring (stiffness 60, damping 18, mass 1.2), useTransform, useInView, avec des décalages de 80 ms par chiffre.
  • Supporte tout format de chaîne, '10,000+', '99%', '48h', les caractères non numériques sont rendus inline, les chiffres reçoivent leur propre colonne animée.
  • La grille ajuste automatiquement les colonnes : 1-2 stats = compte exact, 3 stats = 3 colonnes, 4 et plus = 4 colonnes.
  • Accessible : les valeurs textuelles statiques restent dans le DOM ; l'animation est purement visuelle via des transforms CSS.

Stats Slot Machine est une section stats React déclenchée au scroll où chaque chiffre individuel descend le long d'une colonne de 0 à 9, venant se bloquer sur sa cible avec un rebond élastique. Le stagger se déclenche de droite à gauche pour que le chiffre des unités se pose en premier, donnant à un nombre multi-digits l'apparence d'une vraie machine à sous qui se résout. La section s'intègre bien après un hero SaaS ou une grille de features quand la preuve sociale doit marquer.

Anatomie

Le composant est organisé sur trois niveaux. La section extérieure gère les tokens de layout (fond, padding vertical, largeur max du conteneur). Une zone d'en-tête centrée contient un badge optionnel, un h2 et un sous-titre, qui apparaissent ensemble en fondu. En dessous, une grille CSS de composants StatCard adapte son nombre de colonnes au nombre de stats passées. Chaque carte porte une fine ligne d'accent en haut (un fondu dégradé vers la couleur d'accent), le nombre animé en grand caractère gras, un label court et une description optionnelle.

Comment ça marche

Un seul ref useInView sur le conteneur de grille se déclenche quand la grille franchit le seuil du viewport (margin -80px). Ce booléen se propage à chaque DigitColumn. Chaque colonne maintient son propre useSpring(0, { stiffness: 60, damping: 18, mass: 1.2 }) et utilise useTransform pour le convertir en pourcentage CSS translateY (-v * 10%), faisant défiler une colonne empilée de dix chiffres (0 à 9). Quand inView passe à true, un setTimeout se déclenche avec un délai calculé : le chiffre le plus à droite démarre immédiatement, chaque position à gauche ajoute 80 ms, et chaque carte de la grille ajoute un délai de base de 120 ms supplémentaires. Les caractères non numériques contournent la colonne et s'affichent comme des spans inline statiques.

Comment le coder en React

  1. Tokeniser la chaîne de valeur

    Découpe la valeur stat caractère par caractère. Chaque caractère devient un token taggé chiffre ou non-chiffre. Cela permet à '10,000+' de produire des colonnes animées pour les six chiffres pendant que la virgule et le signe plus s'affichent en texte brut, en conservant le formatage exact sans aucune logique regex.

    function tokenize(value: string) {
      return value.split("").map((char) => {
        const digit = parseInt(char, 10);
        return { char, isDigit: !isNaN(digit), digit: isNaN(digit) ? 0 : digit };
      });
    }
  2. Construire la DigitColumn

    Chaque chiffre animé est une petite boîte de découpe (height: 1em, overflow: hidden) contenant un motion.span qui empile les dix chiffres verticalement. Un useSpring pilote le transform y de 0 vers la valeur du chiffre cible, en translateant de -v * 10% pour faire défiler le bon chiffre dans la vue. Les paramètres du spring (stiffness 60, damping 18, mass 1.2) produisent un départ lent et un arrêt rebondissant satisfaisant.

    const spring = useSpring(0, { stiffness: 60, damping: 18, mass: 1.2 });
    const y = useTransform(spring, (v) => `${-v * 10}%`);
    
    useEffect(() => {
      if (inView) {
        const id = setTimeout(() => spring.set(digit), delay);
        return () => clearTimeout(id);
      } else {
        spring.jump(0);
      }
    }, [inView, digit, delay, spring]);
  3. Décaler de droite à gauche entre chiffres et cartes

    Dans SlotMachineNumber, compte uniquement les tokens numériques et attribue à chacun un délai qui diminue à mesure que l'index se rapproche du côté droit : baseDelay + (digits.length - 1 - localIndex) * 80. Passe l'index de grille de chaque carte comme baseDelay = index * 120 pour que les cartes elles-mêmes se décalent aussi.

    const staggerDelay = baseDelay + (digits.length - 1 - localIndex) * 80;
  4. Brancher useInView sur le conteneur de grille

    Attache un unique ref au div de la grille. Passe-le à useInView avec once: true et margin: '-80px' pour que l'animation se déclenche juste avant que la grille entre entièrement dans le viewport. Toutes les DigitColumns lisent ce seul booléen, pas besoin d'IntersectionObservers individuels par stat.

    const gridRef = useRef<HTMLDivElement>(null);
    const inView = useInView(gridRef, { once: true, margin: "-80px" });

Quand l'utiliser

À utiliser pour ancrer la crédibilité avec des chiffres précis sur une landing SaaS, d'agence ou corporate, placée après le hero ou un bloc features. L'animation est volontaire et accrocheuse, une seule instance par page suffit. À éviter sur les pages avec plusieurs sections animées en compétition, et inutile pour des chiffres qui se mettent à jour en temps réel (l'animation est one-shot). Les nombres de trois chiffres ou plus profitent le plus de l'effet stagger ; les statistiques à un seul chiffre perdent l'essentiel de l'impact.

Utilisé par

  • Stripe, Utilise des compteurs de chiffres animés dans ses sections stats sur ses pages marketing pour mettre en avant les volumes de paiement et les chiffres de disponibilité.
  • Lottiefiles, Affiche la taille de sa communauté et les volumes d'assets avec des animations de chiffres défilants déclenchées au scroll sur sa page d'accueil.
  • Webflow, Des blocs stats avec des chiffres animés en cascade apparaissent sur ses pages entreprise et tarifs.

FAQ

Pourquoi le stagger va-t-il de droite à gauche plutôt que de gauche à droite ?

Le chiffre des unités (à droite) porte le plus de poids visuel dans un grand nombre, donc le voir se poser en premier crée un moment de satisfaction. Gauche à droite paraît mécanique car voir le chiffre de poids fort arriver en premier donne une impression de chargement, pas de résolution.

Peut-on l'utiliser pour des nombres qui changent dynamiquement (compteurs en direct) ?

L'implémentation actuelle est one-shot : inView se déclenche une fois et le spring revient avec spring.jump(0) sans animer. Pour un compteur en direct, il faudrait supprimer le flag once:true sur useInView, piloter la cible du spring depuis une source de données, et supprimer les délais setTimeout.

Comment ajouter un symbole de devise ou une unité avant le nombre ?

Passe la chaîne complète incluant le préfixe dans la prop value, par exemple '$10,000'. La fonction tokenize marque '$' comme non-chiffre, il s'affiche donc comme un span inline statique avant les colonnes animées. Aucune prop ou configuration supplémentaire n'est nécessaire.

L'animation se rejoue-t-elle si l'utilisateur scrolle et revient ?

Non. useInView est configuré avec once: true, donc l'animation se déclenche exactement une fois par chargement de page quand la grille entre dans le viewport pour la première fois. Cela évite l'expérience désagréable de voir les stats se rejouer chaque fois que l'utilisateur passe sur la section.

"use client";

import { useRef, useEffect } from "react";
import { motion, useInView, useSpring, useTransform } from "framer-motion";

interface StatItem {
  id: string;
  value: string;
  label: string;
  description?: string;
}

interface StatsSlotMachineProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  stats?: StatItem[];
}

const EASE_OUT = [0.16, 1, 0.3, 1] as const;

// Splits a string like "10,000+" into character tokens, preserving non-digit chars

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Avis

Animation chiffres machine à sous React, Code + Tutoriel