Pulse Ring
Cinq anneaux CSS concentriques s’élargissent et s’estompent au rythme d’un battement simulé — un indicateur visuel pulsant en pur CSS et JS, sans canvas.
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. Atmosphère comprend 57 effets, dont 10 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Un pool de cinq div.pulse-ring est créé dynamiquement au démarrage (en plus des cinq présents dans le HTML source). Chaque anneau est un cercle CSS pur : border: 3px solid #6366f1 sur fond transparent, border-radius: 50 %, opacity: 0 par défaut. La détection de battement (detectBeat) calcule un intervalle variable : 500 + Math.sin(time × 0.001) × 100 ms, ce qui produit 1,7 à 2,5 battements par seconde, simulant un tempo de 100 à 150 BPM.
À chaque battement détecté, le premier anneau inactif du pool reçoit active = true et un startTime horodaté. Chaque frame du requestAnimationFrame, la fonction calcule un progress linéaire : progress = (time − startTime) / 1000 sur une fenêtre d’une seconde. L’opacité descend de 1 à 0 et l’échelle monte de 1× à 2× — entièrement via des style.opacity et style.transform écrits à chaque frame. Aucune @keyframes CSS n’est utilisée.
L’interpolation est strictement linéaire : ni ease-in ni ease-out. Le diamètre de départ est fixé par width: 120px; height: 120px sur .pulse-ring-container, chaque anneau partant avec inset: 0 (120 × 120 px) et grandissant jusqu’à 240 × 240 px à progress = 1 avant de se réinitialiser.
L’animation tourne dans une boucle requestAnimationFrame partagée avec jusqu’à neuf autres visualiseurs audio de la même IIFE (barres de fréquences, visualiseur circulaire, onde, burst de particules…). Tous partagent la même variable globale lastBeat, ce qui synchronise l’ensemble des visualiseurs sur le même tempo simulé.
Accessibilité
- prefers-reduced-motion absent du code — l’IIFE ne vérifie ni la media query CSS ni
window.matchMedia: l’animation tourne quelle que soit la préférence OS. Pour un usage en production, ajouter@media (prefers-reduced-motion: reduce) { .pulse-ring { display: none; } }ou conserver le résultat dematchMediapour stopper le rAF. - Aucun attribut
aria-*sur le conteneur ni sur les anneaux — l’effet est invisible aux lecteurs d’écran. Ajouteraria-hidden="true"sur.pulse-ring-containers’il est purement décoratif. - Aucune interaction clavier — l’effet est passif et décoratif, ce qui est correct pour un indicateur audio. Si le conteneur reçoit un
tabindex, le retirer ou masquer l’élément aux technologies d’assistance. - Contraste des anneaux (#6366f1 sur fond transparent) : élément décoratif non-textuel, WCAG 1.4.3 ne s’applique pas. Si l’anneau sert d’indicateur fonctionnel (signal actif/inactif), WCAG 1.4.11 (rapport ≥ 3:1 entre l’anneau et le fond réel) s’applique.
- Aucun
roleniaria-labelsur le conteneur — ajouter ces attributs si l’animation transmet une information signifiante (ex. : détection de battement, signal actif en cours).
Compatibilité navigateur
Nécessite requestAnimationFrame, document.createElement, Array.prototype.find et la propriété CSS inset — couverts par tous les navigateurs modernes.
Sans <code>requestAnimationFrame</code> (navigateurs très anciens), la boucle ne démarre pas et les anneaux restent invisibles (<code>opacity: 0</code>) — aucune erreur JS fatale, la page reste fonctionnelle. <code>inset</code> nécessite Chrome 87+, Firefox 87+, Safari 14.1+ ; <code>transform: scale()</code> est universel.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="pulse-ring-container" id="pulseRingContainer">
<div class="pulse-ring"></div>
<div class="pulse-ring"></div>
<div class="pulse-ring"></div>
<div class="pulse-ring"></div>
<div class="pulse-ring"></div>
<!-- Le JS ajoute 5 anneaux supplémentaires au démarrage (seuls ceux-ci sont animés) -->
</div>
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éfaut | Effet |
|---|---|---|
| border: 3px solid #6366f1 (CSS .pulse-ring) | 3px solid #6366f1 | Couleur et épaisseur de chaque anneau. Remplacez la valeur hex pour coller à votre charte ; montez à 4–6 px pour un trait plus affirmé. |
| width / height (CSS .pulse-ring-container) | 120px × 120px | Diamètre de départ des anneaux. Montez à 180–240 px pour un usage hero ; réduisez à 60–80 px pour un indicateur compact dans une carte. |
| elapsed / 1000 (JS animatePulseRing) | 1000 ms | Durée d’animation de chaque anneau. Baissez à 600 ms pour un battement nerveux ; montez à 1 500–2 000 ms pour un effet lent et hypnotique. |
| scale(${1 + progress}) (JS animatePulseRing) | échelle finale 2× (delta 1) | Expansion maximale de l’anneau. Remplacez par scale(${1 + 2 * progress}) pour tripler la taille finale ; 1 + 0.5 * progress pour une expansion plus discrète. |
| 500 + Math.sin(time * 0.001) * 100 (JS detectBeat) | 500 ± 100 ms | Intervalle inter-battements. Baissez le centre à 350 pour ~170 BPM ; montez à 800 pour un rythme lent (~75 BPM). Remplacez toute la formule par une constante fixe pour un tempo régulier. |
| for (let i = 0; i < 5; i++) (JS initialisation pool) | 5 anneaux | Taille du pool d’anneaux. Montez à 8–10 pour plus de chevauchement simultané ; descendez à 3 pour un rendu épuré. |
FAQ
getElementById(’pulseRingContainer’) et un tableau rings fermé dans l’IIFE — une seule instance est gérée par défaut. Pour plusieurs instances, extrayez la logique dans une fonction usine qui prend un élément en paramètre, crée son propre pool et partage la boucle requestAnimationFrame : la détection de battement via lastBeat reste partagée, gardant toutes les instances synchronisées sur le même tempo.animatePulseRing écrit style.opacity et style.transform directement sur chaque anneau à chaque frame du requestAnimationFrame — aucune @keyframes, aucune propriété animation CSS, aucun transition. Cela donne un contrôle total sur le timing et l’easing, au prix d’une dépendance forte à la boucle rAF.detectBeat par une fonction qui analyse l’énergie d’un flux AudioContext. Connectez votre source à un AnalyserNode, calculez le RMS des données temporelles à chaque frame, et déclenchez un battement quand le RMS dépasse un seuil. La variable lastBeat peut rester pour éviter des déclenchements trop rapprochés.