Bento with Stats
Grille bento de statistiques : les compteurs s'animent au défilement via IntersectionObserver, les barres de progression se révèlent au survol en transition CSS.
Mis à jour le
Cet effet fait partie d'Effect.Labs — 840 effets vanilla, une partie gratuite, une partie premium. Cartes comprend 43 effets, dont 6 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
La mise en page repose sur un CSS Grid à 4 colonnes et 3 lignes (.bento-stats) : cell-1 occupe 2 colonnes × 2 lignes, cell-2 2 colonnes, cell-3 et cell-4 1 colonne chacune, cell-5 et cell-6 2 colonnes. Les chiffres (.stat-number) utilisent -webkit-text-fill-color: transparent combiné à background-clip: text pour afficher le gradient --gradient-primary en forme de texte.
Un IntersectionObserver surveille l'élément #statsBento avec un seuil de 0,5 : quand la grille est visible à 50 % dans le viewport, la fonction animateStats() se déclenche une seule fois (l'observer se désabonne immédiatement via unobserve). Elle lit l'attribut data-target de chaque .stat-number pour connaître la valeur cible.
L'animation de chaque compteur est pilotée par un setInterval de 60 pas sur 1 500 ms. À chaque pas, current = (target / 60) × step. Si la valeur cible est décimale (target % 1 !== 0), le texte est formaté avec toFixed(1) ; sinon avec Math.floor. L'intervalle se nettoie lui-même à l'étape 60.
Les barres de progression (.stat-bar-fill) démarrent à width: 0 et passent à width: var(--fill-width) dès qu'une cellule reçoit le focus ou le survol (sélecteur CSS .bento-cell:hover .stat-bar-fill). La valeur --fill-width est définie en style inline sur chaque barre — aucun JS supplémentaire requis. La transition dure 1 s avec une courbe cubic-bezier(0.16, 1, 0.3, 1).
Accessibilité
- prefers-reduced-motion non géré : le code source ne contient pas de vérification de ce media query. Les compteurs s'animent même si l'OS signale une préférence de réduction du mouvement. Ajoutez un guard
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;en tête deanimateStats()pour le désactiver. - Aucun
roleniaria-labelsur le conteneur : les lecteurs d'écran peuvent annoncer les valeurs intermédiaires (0 → valeur finale) pendant l'animation. Envisagezaria-live="off"sur chaque.stat-numberpour masquer les mises à jour dynamiques. - Le texte gradient (
-webkit-text-fill-color: transparent) dépend de la luminosité du gradient choisi. Vérifiez un ratio ≥ 4,5:1 entre la teinte la plus sombre du gradient et le fond--bg-card(#12121a) avant de changer--gradient-primary. - Les barres de progression sont uniquement accessibles au survol souris. Les cellules n'étant pas focusables (
tabindexabsent), les utilisateurs clavier ne voient jamais les barres. Ajoutez:focus-within .stat-bar-fillsi cet accès est requis. - Sans JavaScript, les chiffres restent à 0 et les barres restent masquées — aucune erreur JS fatale, la grille s'affiche correctement avec son fond et ses labels.
Compatibilité navigateur
Nécessite CSS Grid, CSS Custom Properties, IntersectionObserver et background-clip: text (préfixe -webkit- inclus). Zéro dépendance externe.
Sans IntersectionObserver (navigateurs antérieurs à 2017), les compteurs ne démarrent pas — les chiffres restent à 0. Polyfill optionnel via @types/intersection-observer si un support IE11 est nécessaire. La grille et les barres restent visibles.
Le code
Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :
<div class="demo-preview">
<div class="bento-container">
<div class="bento-grid bento-stats" id="statsBento">
<!-- Cellule principale 2×2 -->
<div class="bento-cell cell-1">
<span class="stat-number" data-target="2847">0</span>
<span class="stat-label">Label principal</span>
<div class="stat-bar">
<div class="stat-bar-fill" style="--fill-width: 95%;"></div>
</div>
</div>
<!-- Cellule 2 colonnes -->
<div class="bento-cell cell-2">
<span class="stat-number" data-target="156">0</span>
<span class="stat-label">Label</span>
</div>
<!-- Cellules 1 colonne -->
<div class="bento-cell cell-3">
<span class="stat-number" data-target="98">0</span>
<span class="stat-label">%</span>
</div>
<div class="bento-cell cell-4">
<span class="stat-number" data-target="42">0</span>
<span class="stat-label">Label</span>
</div>
<!-- Cellules 2 colonnes avec barre -->
<div class="bento-cell cell-5">
<span class="stat-number" data-target="1.2">0</span>
<span class="stat-label">M Label</span>
<div class="stat-bar">
<div class="stat-bar-fill" style="--fill-width: 72%;"></div>
</div>
</div>
<div class="bento-cell cell-6">
<span class="stat-number" data-target="4.9">0</span>
<span class="stat-label">Note / 5</span>
<div class="stat-bar">
<div class="stat-bar-fill" style="--fill-width: 98%;"></div>
</div>
</div>
</div>
</div>
</div>
Le code complet HTML + CSS + JS de Bento with Stats, prêt à coller — et les 840 effets. Ou 299 € TTC une fois, accès à vie.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| data-target | valeur chiffrée | Nombre affiché en fin d'animation sur chaque .stat-number. Entier → Math.floor ; décimal → toFixed(1). Exemples : 2847, 98, 1.2, 4.9. |
| --fill-width (inline) | 95 %, 72 %, 98 % | Largeur finale de la barre de progression, définie en style inline sur chaque .stat-bar-fill. Exemple : style="--fill-width: 75%". |
| --gradient-primary | linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%) | Gradient appliqué aux chiffres (background-clip: text) et aux barres de progression. Remplacez par vos couleurs brand. |
| --bg-card | #12121a | Fond de chaque cellule bento. Augmentez légèrement la luminosité (#1c1c2a) pour un thème moins sombre. |
| --text-muted | #8b8b93 | Couleur des labels de statistiques (.stat-label). Utilisez une teinte plus claire si le fond de carte est plus sombre. |
| duration (JS, ligne const duration) | 1500 | Durée totale d'animation des compteurs en ms. 1 000 ms = vif et percutant, 2 000 ms = cinématique. |
| threshold (IntersectionObserver) | 0.5 | Fraction de visibilité de la grille requise avant déclenchement. 0 = dès qu'un pixel entre dans le viewport, 1 = grille entièrement visible. |
| grid-template-columns (.bento-stats) | repeat(4, 1fr) | Nombre et proportion des colonnes. Passez à repeat(3, 1fr) pour une grille plus compacte (adaptez les grid-column: span N des cellules en conséquence). |
FAQ
IntersectionObserver appelle statsObserver.unobserve(entry.target) dès le premier déclenchement — les compteurs ne repartent jamais de zéro, même si l'utilisateur scrolle au-delà puis revient sur la grille.<div class="bento-cell cell-N"> avec un .stat-number[data-target] et un .stat-label. Définissez son placement dans .bento-stats .cell-N via grid-column: span X et/ou grid-row: span Y. Si vous changez le nombre de colonnes (grid-template-columns), vérifiez que les spans restent cohérents avec la nouvelle grille.animateStats est encapsulée dans l'IIFE. Exposez-la vous-même : déplacez la déclaration en dehors de l'IIFE (window.animateStats = function() { … }) ou appelez document.dispatchEvent(new Event('resetStats')) en écoutant cet événement avant de relancer l'animation après avoir remis les chiffres à 0.