Navigation✨ Premium

Dynamic Badge Positioning

Pastilles de compteur et de statut positionnées aux coins d'avatars circulaires — clic pour incrémenter avec animation spring rejouée par reflow forcé.

CSSBadgeResponsive

Vous aurez accès à la démo interactive avec un compte gratuit.

Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Navigation comprend 36 effets, dont 4 gratuits. Découvrir la catégorie →

3 exemples d'usage

Sidebar de messagerie — Contacts et messages non lus — Dynamic Badge Positioning exemple 1

① Sidebar de messagerie — Contacts et messages non lus

QuandLa barre latérale d'une application de chat liste les contacts avec leur nombre de messages non lus et leur statut de présence.
PourquoiLes badges se repositionnent automatiquement si la taille des avatars change (vue compacte vs confort) — zéro ajustement JS. Le rebond spring donne un retour immédiat à chaque nouveau message entrant.
RéglagesAvatars 48 px (compact) ou 56 px (confort). Compteurs initialisés depuis l'API. Durée badgePop réduite à 0,3s pour une UI dense. Statuts : .online #10b981, .busy #f59e0b, .offline #6b7280.
Widget équipe — Grille de disponibilités — Dynamic Badge Positioning exemple 2

② Widget équipe — Grille de disponibilités

QuandUn panneau de présence dans un outil de gestion de projet affiche les membres disponibles, occupés et absents en temps réel.
PourquoiLes pastilles de statut ancrées au coin bas-droit des avatars signalent d'un coup d'œil qui est disponible sans agrandir les cartes membres. Le compteur de badge peut refléter des tâches assignées.
RéglagesAvatars 56 px, grille 3 colonnes. badgePop 0,25s pour les mises à jour fréquentes (polling ou WebSocket). Couleurs de statut adaptables à la convention de l'équipe.
Fil de discussion — Auteurs et compteurs de réponses — Dynamic Badge Positioning exemple 3

③ Fil de discussion — Auteurs et compteurs de réponses

QuandUne plateforme de blog ou de forum affiche les auteurs de commentaires avec leur nombre de contributions récentes.
PourquoiLe clic sur l'avatar rejoue le spring et attire l'attention sur la contribution — feedback immédiat sans rechargement de page. Le compteur incrémental simule l'arrivée de nouvelles réponses.
RéglagesCouleur du badge #3b82f6 (bleu) pour distinguer les réponses des notifications urgentes. Avatars 48 px, .badge-label masqué en liste dense.

Comment ça marche

Chaque .badge-avatar est en position: relative, ce qui crée un contexte de positionnement pour ses badges enfants. Le .badge-count se pose à top: -4px; right: -4px et le .badge-status à bottom: 0; right: 0 — coordonnées en pixels relatives aux coins de l'avatar. Si la taille de l'avatar change, les badges suivent automatiquement les coins sans aucun recalcul JS.

L'animation d'entrée du compteur, @keyframes badgePop, passe de scale(0) à scale(1) avec la courbe cubic-bezier(.34, 1.56, .64, 1). Le deuxième chiffre de contrôle (1.56) dépasse intentionnellement 1.0 : la courbe outrepasse la valeur cible avant de revenir en arrière, produisant un spring bounce sans aucune librairie d'animation.

Chaque clic incrémente le compteur puis rejoue l'animation en 3 étapes : annulation (badge.style.animation = 'none'), lecture de badge.offsetHeight pour forcer un reflow synchrone qui purge la valeur calculée du style, puis réassignation de la chaîne d'animation. Sans ce reflow, le navigateur fusionne les deux changements de style en une seule passe et ne rejoue pas une animation déjà terminée.

Le survol grossit l'avatar (scale(1.12)) et le badge (scale(1.2)) via des transitions CSS, toutes deux avec la même courbe cubic-bezier(.34, 1.56, .64, 1). Le badge grossit via le sélecteur .badge-avatar:hover .badge-count — un retour visuel secondaire sans une ligne de JS supplémentaire.

Accessibilité

  • prefers-reduced-motion absent : le code ne détecte pas la préférence système de réduction de mouvement. L'animation badgePop et les transitions hover jouent sans condition. Pour respecter WCAG 2.3.3, ajoutez @media (prefers-reduced-motion: reduce) { .badge-count { animation: none; } .badge-avatar { transition: none; } }.
  • Les .badge-avatar sont des <div> cliquables sans role="button" ni tabindex="0" — ils ne sont ni focusables au clavier ni annoncés par les lecteurs d'écran. Pour un usage production, convertissez-les en <button> ou ajoutez ces deux attributs.
  • Contraste du compteur : texte blanc (#fff) sur fond rouge (#ef4444) → ratio ≈ 4,5:1, niveau WCAG AA atteint pour du texte en gras de taille normale.
  • Les .badge-label (En ligne, Occupé, Absent) fournissent un contexte textuel visible mais ne sont pas reliés à l'avatar via aria-describedby — un lecteur d'écran ne les associera pas automatiquement à l'avatar et à son compteur.

Compatibilité navigateur

Repose sur position: absolute, CSS animation, CSS transition et le reflow via offsetHeight — combinaison universellement supportée depuis 2014.

Chrome 43+✓ Complet
Firefox 44+✓ Complet
Safari 9+✓ Complet
Edge 13+✓ Complet
Mobile iOS✓ Touch OK
Android Chrome✓ Complet

Si les animations CSS sont désactivées (OS ou navigateur très ancien), les badges apparaissent à leur position finale sans spring — la disposition reste parfaitement lisible et fonctionnelle.

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
<div class="badge-scene">
  <div class="badge-avatar badge-avatar-1">
    <!-- Emoji, image ou initiales -->
    <span class="badge-count">3</span>
    <span class="badge-status online"></span>
    <span class="badge-label">En ligne</span>
  </div>
  <!-- Répéter .badge-avatar pour chaque utilisateur -->
</div>
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
.badge-count { background } #ef4444 Couleur de fond du compteur de notifications. Remplacez par votre teinte brand : #3b82f6 (bleu), #10b981 (vert), #8b5cf6 (violet).
.badge-status.online/busy/offline { background } #10b981 / #f59e0b / #6b7280 Couleurs des trois états de présence. Adaptez à votre convention d'interface.
.badge-avatar { width / height } 60px Taille de l'avatar circulaire. Les badges suivent les coins à n'importe quelle taille — aucun recalcul JS.
.badge-count { animation: .4s … } 0.4s Durée de l'animation spring badgePop. Réduisez à 0,25s pour une UI dense et réactive ; augmentez à 0,6s pour un accent dramatique.
cubic-bezier(.34, 1.56, .64, 1) 1.56 = overshoot Deuxième chiffre > 1 = amplitude du rebond spring. 2.0 = très élastique ; 1.1 = subtil. Modifier dans animation et transition.
.badge-count { top / right } -4px / -4px Décalage en pixels par rapport à l'angle supérieur droit de l'avatar. Ajustez si le badge est tronqué par un overflow parent.
.badge-count { border } 2px solid #0a0a0f Bordure de séparation entre le badge et l'avatar. La couleur doit correspondre au fond de la scène pour l'effet de halo.
.badge-label { color } #a5b4fc Couleur du texte d'état sous l'avatar. Adaptez à votre thème (blanc, gris neutre, couleur accent).

FAQ

Oui. .badge-count et .badge-status utilisent position: absolute avec des coordonnées en pixels relatives aux coins de .badge-avatar (son conteneur position: relative). Modifiez width et height de .badge-avatar : les badges suivent automatiquement, sans recalcul JS.
Le JS annule l'animation (badge.style.animation = 'none'), lit badge.offsetHeight pour forcer un reflow synchrone qui purge la valeur calculée, puis réassigne la chaîne d'animation. Sans ce reflow, le navigateur fusionne les changements de style en une seule passe et saute le re-déclenchement — c'est la méthode fiable sans setTimeout ni toggle de classe.
Le script s'exécute une fois au chargement avec querySelectorAll('.badge-avatar'). Pour des avatars insérés ultérieurement, réattachez l'écouteur manuellement sur chaque nouvel élément. Pour des insertions fréquentes, utilisez la délégation d'événement sur le parent : container.addEventListener('click', e => { const av = e.target.closest('.badge-avatar'); if (!av) return; /* logique badge */ }).