ScrollGratuit

Concentric Rings Morph

Cinq cercles SVG de rayon 14 à 58, tracés depuis midi dans le sens horaire par un stroke-dasharray égal à leur circonférence multipliée par --p ; sur 624 px de défilement fantôme, la teinte oklch tourne de 120°, la figure passe de 139 à 193 px et chaque anneau pivote de 18° de plus que le précédent — 681 octets de JavaScript, rien qui dépende d'animation-timeline.

ScrollSVGOKLCH
Scrollez ici ↓
Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Scroll en contient 70 au total, dont 12 gratuits. Effect.Labs regroupe 811 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Aperçu : en défilant, cinq anneaux concentriques se tracent du centre vers l'extérieur — le signal 5G+ qui gagne le centre-ville, jusqu'à 100 %.
Scrollez ici ↓

① Couverture 5G+ sur la page d'offre d'un opérateur

QuandPage d'offre mobile : accroche, promesse de couverture, bouton vers la carte ; à droite, cinq anneaux se tracent du centre vers l'extérieur au fil du défilement et le compteur central monte jusqu'à « 100% » du centre-ville couvert.
PourquoiLe tracé progressif d'anneaux radiaux est la métaphore exacte d'un signal qui se propage — aucun autre effet du lot ne dessine une figure géométrique vectorielle : Corner Morph Overlay (fx-0779) révèle un calque déjà là depuis un coin, il ne trace pas. Les queues qui pivotent de 18° par anneau donnent le léger déphasage d'ondes successives.
RéglagesTracé en vagues qui se chevauchent : var(--p) remplacé dans stroke-dasharray par clamp(0, var(--p) × 3 − var(--i) × .5, 1) (l'anneau i part à p = i/6 et met un tiers de course) ; teintes oklch(78% .16 calc(215 + var(--i) × 6 + var(--p) × 30)), du bleu au cyan ; plancher d'opacité .25 ; SVG et compteur décalés à droite (left: auto; right: 44px) ; piste 300%.
Aperçu : en défilant, les cinq anneaux de l'objectif de collecte se remplissent l'un après l'autre, jusqu'aux 34 200 € réunis sur 50 000 € — 68 %.
Scrollez ici ↓

② Objectif de collecte d'une association — un anneau par tranche

QuandPage de campagne : titre de la collecte, montant réuni, bouton de don ; à gauche, cinq anneaux valent 10 000 € chacun et se remplissent l'un après l'autre au défilement, jusqu'aux 34 200 € atteints — trois anneaux pleins et 42 % du quatrième.
PourquoiDes anneaux qui se dessinent traduisent un cumul en cours et laissent voir ce qui manque : les rails gris derrière sont les tranches à venir. La barre de progression textuelle de fx-0575 est plus sobre, mais un appel aux dons doit émouvoir, et cinq cercles qui se ferment un à un le font mieux qu'un rectangle.
RéglagesPaliers stricts : --k: clamp(0, var(--p) × 5 × .684 − var(--i), 1) (0,684 = 34 200 / 50 000) dans stroke-dasharray et l'opacité ; compteur Math.round(p × 68) + '%' dans une instance du script sur l'id crmScrollDons ; cinq cercles-rails statiques ajoutés dans le SVG ; teintes oklch(62% .19 calc(30 + var(--i) × 7 + var(--p) × 25)), clarté baissée pour le fond crème ; SVG à gauche (right: auto; left: 40px).
Aperçu : en défilant, les quatre niveaux de fidélité se remplissent dans l'ordre — Bronze et Argent complets, Or à 56 %, Platine vide — jusqu'à 640 points.
Scrollez ici ↓

③ Niveaux d'un programme de fidélité sur la page de compte

QuandCarte « Mon statut » d'un compte client : Bronze, Argent, Or et Platine sont quatre anneaux imbriqués qui se remplissent dans l'ordre au défilement ; le compteur monte jusqu'aux 640 points du client — Bronze et Argent fermés, Or à 56 %, Platine vide.
PourquoiQuatre anneaux concentriques disent « niveaux imbriqués » sans légende : chaque niveau contient le précédent. La barre horizontale de Snap + Progress (fx-0597) rend une progression linéaire, sans idée de paliers emboîtés. Une couleur propre à chaque métal remplace le glissement de teinte, qui n'aurait pas de sens ici.
RéglagesQuatre cercles de rayon 20, 32, 44, 56 et longueurs 126 + var(--i) × 75 (leurs circonférences) ; --k: clamp(0, var(--p) × 2.56 − var(--i), 1) (640 pts sur des paliers de 250) ; couleur par anneau via trois variables en ligne, oklch(var(--l) var(--c) calc(var(--h) + var(--p) × 12)) ; compteur Math.round(p × 640) + ' pts' sur l'id crmScrollFid ; rails à 8 % de blanc.

Comment ça marche

La scène .crm-scene (430 × 260 px, overflow: hidden, --p: 0) empile le même squelette que fx-0778, fx-0779 et fx-0780 : au-dessus, .crm-scroll (z-index: 3, overflow-y: auto, scrollbar-width: thin) ne contient qu'un bloc vide .crm-track haut de 340% — 884 px de contenu pour 260 px de fenêtre, soit 624 px de défilement fantôme ; dessous, .crm-stage (z-index: 1, pointer-events: none, aria-hidden) porte un SVG de 200 × 200 px en viewBox 0 0 120 120 — une unité vaut 1,667 px — avec cinq <circle> de rayon 14, 25, 36, 47 et 58 (un pas de 11), chacun portant son indice --i de 0 à 4, en fill: none, stroke-width: 5, stroke-linecap: round ; par-dessus, .crm-pct (grille centrée, 16 px, graisse 800) affiche attr(data-pct) dans son ::after, et .crm-hint « Scrollez ici ↓ » (9,6 px) s'efface en opacity: calc(1 − var(--p) × 3) — mesuré 0,52 à p = 0,16, 0,04 à 0,32, 0 dès 0,333. Le script fait 19 lignes ES5 : getElementById('crmScroll'), closest('.crm-scene'), un écouteur scroll passif qui ne fait que réserver une image par requestAnimationFrame (mesuré : 30 événements dans la même image → une seule écriture), et update() qui calcule p = scrollTop / (scrollHeight − clientHeight), l'écrit à quatre décimales dans --p sur la scène et pose Math.round(p × 100) + '%' dans data-pct. À la molette par crans de 100 px : p avance de 0,1603 par cran, la course est bouclée au 7ᵉ, huit écritures en tout — identiques dans Chromium, Firefox et WebKit, sans une erreur console.

Le tracé tient dans une déclaration : stroke-dasharray: calc((88 + var(--i) × 69) × var(--p) × 1px) 1000px. Dans un SVG, px est l'unité utilisateur du viewBox, et 88 + 69 × i est la circonférence de chaque cercle à l'unité près (2π × 14 = 87,96 … 2π × 58 = 364,42 : l'anneau extérieur manque de 0,42 unité, que les bouts ronds de 2,5 unités de rayon referment) ; l'espace de 1 000 dépasse toute circonférence, il n'y a donc jamais qu'un seul trait par cercle. À p, le trait mesure p fois la circonférence : les cinq têtes avancent à la même vitesse angulaire, 360° sur 624 px, 0,58° par pixel de défilement, quel que soit le rayon — mesuré à p = 0,5, l'anneau intérieur est allumé de 0° à 192° et l'extérieur de 36° à 216° (échantillonnage à 6°, bouts ronds compris). Le chemin d'un <circle> commence à 3 h et tourne dans le sens horaire ; transform: rotate(calc(−90deg + var(--i) × 18deg × var(--p))) autour de 60px 60px ramène le départ à midi et fait pivoter l'anneau i de 18° × i × p : les queues s'écartent en éventail (celle de l'anneau extérieur est à 18° à p = 0,25, 36° à 0,5, 72° à 1), écart invisible une fois les anneaux fermés. scale(calc(.72 + var(--p) × .28)) fait grandir la figure de 139 à 193 px de diamètre et opacity: calc(.35 + var(--p) × .65) l'éclaircit. Au repos, le trait est de longueur nulle mais les bouts ronds subsistent : un point de 6 px à midi sur chacun des cinq anneaux, cinq points empilés au-dessus du compteur (mesuré : pixels allumés au sommet de chaque cercle à p = 0, rgb(61,58,130) pour l'intérieur, 1,8 à 2,0:1 sur le fond).

stroke: oklch(75% .26 calc(280 + var(--i) × 26 + var(--p) × 120)) fixe la clarté (75 %) et la chroma (.26) et ne fait tourner que la teinte : 280 + 26 × i au repos — 280 (violet), 306, 332, 358 et 24 (rouge orangé) — plus 120° sur la course, soit 40, 66, 92, 118 et 144 à l'arrivée, de l'orange au vert. Une chroma de .26 à 75 % de clarté sort du gamut sRGB pour toutes ces teintes : le navigateur écrête, et les couleurs mesurées dans Chromium à p = 1 — rgb(255,89,0), rgb(255,124,0), rgb(233,160,0), rgb(163,191,0), rgb(0,212,17) — portent la trace de ce clip (composantes à 255 ou 0) ; sur un écran P3 elles seront plus saturées et légèrement différentes. Trois limites mesurées. Le bord externe de l'anneau 4 est à 60,5 unités du centre (58 + demi-trait 2,5) pour un viewBox de 60 : le SVG, en overflow: hidden, rogne 0,8 px à p = 1 (pixel allumé à 100 px du centre, fond à 101). Le script vise un id unique : une seconde copie du HTML reste à p = 0 (mesuré : scrollTop 300, --p 0.0000, compteur « 0% », et le document porte deux id="crmScroll"). Enfin oklch() n'a aucun repli : sur un navigateur qui ne le lit pas, la déclaration entière est rejetée, stroke retombe à none et aucun anneau n'apparaît — mesuré : une déclaration stroke invalide laisse la place à la précédente quand il y en a une, donc une ligne stroke: hsl(calc(280 + var(--i) × 26 + var(--p) × 120), 90%, 65%) placée avant rend rgb(193,85,246) là où oklch() manque.

Accessibilité

  • prefers-reduced-motion absent du code — mesuré sous reduce dans Chromium, Firefox et WebKit : à scrollTop 312, p = 0,5, traits, rotation et échelle identiques à ceux d'un visiteur sans préférence. Le mouvement suit la molette ou le doigt, ce n'est pas une animation autonome, mais trois transformations tournent en même temps (tracé, rotation de 18° par anneau, agrandissement de 39 %) sur une figure de 200 px. Repli qui garde l'information et retire le mouvement parasite : @media (prefers-reduced-motion: reduce) { .crm-ring { transform: rotate(-90deg) } } — les anneaux se tracent encore, sans pivoter ni grandir.
  • Clavier vérifié, lecteur d'écran à compléter. tabindex="0" rend la zone focalisable : Tab l'atteint, Flèche bas défile de 40 px par pression dans Chromium (trois pressions → p = 0,19) et de 48 px dans Firefox, Fin envoie à p = 1, et :focus-visible dessine un liseré rgb(167,139,250) de 2 px à l'intérieur du cadre (7,1:1). Dans WebKit, mesuré : la première Flèche bas après Tab est ignorée, la deuxième défile de 40 px. Mais la région est vide pour la synthèse vocale : role="region" et un aria-label en français annoncent un cadre sans contenu (la piste est un div vide), et le compteur vit dans le calque aria-hidden — le pourcentage n'est jamais annoncé. Si le chiffre compte (collecte, niveau), écrivez-le aussi dans la piste, masqué visuellement, ou dans un output aria-live="polite" mis à jour par update() ; et traduisez l'aria-label.
  • Contrastes mesurés sur le fond rgb(13,13,22) : compteur rgb(231,229,255) 16 px = 15,7:1 ; indication rgb(143,143,176) = 6,2:1 mais 9,6 px (0.6rem), à remonter à 11-12 px. Les anneaux sont des éléments graphiques (seuil 3:1) et leur opacité suit p : à p = 1, de 6,2:1 (orange) à 9,6:1 (vert) ; à p = 0,5 (opacité 0,675), 4,0 à 4,4:1 ; à p = 0,25, 2,7 à 2,8:1, et les points de repos 1,8 à 2,0:1 — le premier quart du tracé est sous 3:1. Sur fond blanc, les couleurs finales ne tiennent pas non plus (2,0 à 3,1:1). Si l'anneau porte une information, relevez le plancher d'opacité de .35 à .6 et, sur fond clair, la clarté 75% à 60%.
  • Tactile et cohabitation avec la page — mesuré en émulation mobile : un geste de 250 px défile la zone (p = 0,40), l'effet marche au doigt. La zone absorbe 624 px de molette avant de rendre la main : dans Chromium et WebKit, le cran qui suit p = 1 fait avancer la page de 100 px ; Firefox retient la molette dans le cadre tant que le geste est continu (mesuré : deux crans de plus à 120 ms d'intervalle, page immobile) et la rend après une pause. Pas de piège, mais 624 px de rouleau pour une scène de 260 px : réduisez height: 340% si la scène n'est qu'un indicateur, et n'ajoutez pas overscroll-behavior: contain, qui bloquerait l'utilisateur dont le pointeur repose sur le cadre.

Compatibilité navigateur

Requiert oklch() avec un calc() dans la teinte — l'exigence la plus récente : Chrome 111, Firefox 113, Safari 15.4 —, le raccourci inset, les transformations CSS sur éléments SVG avec transform-origin en px, des calc() qui multiplient une variable par 1px et par 1deg, attr() dans content, closest(), requestAnimationFrame et les écouteurs passifs ; scrollbar-width: thin n'est lu que par Chrome 121+ et Safari 18.2+ (barre standard avant). Zéro dépendance, 681 octets de JavaScript ES5. La progression est lue en JavaScript (scrollTop), pas par animation-timeline: scroll() : l'effet fonctionne dans Firefox, qui n'anime aucune timeline de défilement en CSS natif — pour tracer une figure au défilement, c'est ce qui le sépare de Path Drawing on Scroll (fx-0572), bâti sur animation-timeline et inerte dans ce navigateur.

Chrome 111+✓ Complet
Firefox 113+✓ Complet
Safari 15.4+✓ Complet
Edge 111+✓ Complet
Mobile iOS 15.4+✓ Complet (défilement au doigt)
Android Chrome 111+✓ Complet

Sans JavaScript, --p reste à 0 (l'attribut style du HTML vendu le fixe) : cinq points à midi à 35 % d'opacité, « 0% » au centre, l'indication, et le défilement ne trace rien. Sans oklch() (Chrome ≤ 110, Firefox ≤ 112, Safari ≤ 15.3), la déclaration stroke est rejetée et les anneaux n'existent pas : seul le compteur bouge — ajoutez la ligne hsl() décrite plus haut. Sans inset (Safari ≤ 14.0, Chrome ≤ 86, Firefox ≤ 65), les couches absolues n'ont plus de dimensions : zone de défilement de 0 × 0, effet mort ; top: 0; right: 0; bottom: 0; left: 0 les rendrait.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="crm-scene" style="--p: 0.0000;">
  <div class="crm-scroll" id="crmScroll" tabindex="0" role="region" aria-label="Zone de démonstration au défilement">
    <div class="crm-track" style="height:340%">
    </div>
  </div>
  <div class="crm-stage" aria-hidden="true">
    <svg class="crm-svg" viewBox="0 0 120 120" aria-hidden="true">
      <circle class="crm-ring" style="--i:0" cx="60" cy="60" r="14">
      </circle>
      <circle class="crm-ring" style="--i:1" cx="60" cy="60" r="25">
      </circle>
      <circle class="crm-ring" style="--i:2" cx="60" cy="60" r="36">
      </circle>
      <circle class="crm-ring" style="--i:3" cx="60" cy="60" r="47">
      </circle>
      <circle class="crm-ring" style="--i:4" cx="60" cy="60" r="58">
      </circle>
    </svg>
    <span class="crm-pct" data-pct="0%">
    </span>
  </div>
  <span class="crm-hint">Scrollez ici ↓</span>
</div>
CSS
.crm-scene  {
   position: relative;
   width: 100%;
   max-width: 430px;
   height: 260px;
   border-radius: 12px;
   overflow: hidden;
   border: 1px solid rgba(255, 255, 255, 0.08);
   background: rgb(13, 13, 22);
   --p: 0;
   }

.crm-scroll  {
   position: absolute;
   inset: 0px;
   overflow-y: auto;
   z-index: 3;
   scrollbar-width: thin;
   }

.crm-stage  {
   position: absolute;
   inset: 0px;
   z-index: 1;
   pointer-events: none;
   }

.crm-hint  {
   position: absolute;
   right: 10px;
   bottom: 8px;
   z-index: 2;
   font-size: 0.6rem;
   letter-spacing: 0.12em;
   text-transform: uppercase;
   color: rgb(143, 143, 176);
   opacity: calc(1 - var(--p) * 3);
   pointer-events: none;
   }

.crm-scroll:focus-visible  {
   outline: rgb(167, 139, 250) solid 2px;
   outline-offset: -2px;
   }

.crm-track  {
   width: 100%;
   }

.crm-svg  {
   position: absolute;
   inset: 0px;
   margin: auto;
   width: 200px;
   height: 200px;
   }

.crm-ring  {
   fill: none;
   stroke-width: 5;
   stroke-linecap: round;
   stroke: oklch(75% .26 calc(280 + var(--i) * 26 + var(--p) * 120));
   stroke-dasharray: calc((88 + var(--i) * 69) * var(--p) * 1px) 1000px;
   transform-origin: 60px 60px;
   transform: rotate(calc(-90deg + var(--i) * 18deg * var(--p))) scale(calc(.72 + var(--p) * .28));
   opacity: calc(.35 + var(--p) * .65);
   }

.crm-pct  {
   position: absolute;
   inset: 0px;
   display: grid;
   place-items: center;
   font-size: 1rem;
   font-weight: 800;
   color: rgb(231, 229, 255);
   letter-spacing: 0.08em;
   }

.crm-pct::after  {
   content: attr(data-pct);
   }
JavaScript (fx-0781)
(function() {
  /* fx-0781 — Concentric Rings Morph : progression de scroll → variable CSS --p (rAF, passif) */
  var sc = document.getElementById('crmScroll');
  if (!sc) return;
  var host = sc.closest('.crm-scene');
  var raf = null;
  function update() {
    raf = null;
    var max = sc.scrollHeight - sc.clientHeight;
    var p = max > 0 ? sc.scrollTop / max : 0;
    host.style.setProperty('--p', p.toFixed(4));
    var pct = host.querySelector('.crm-pct');
    if (pct) pct.setAttribute('data-pct', Math.round(p * 100) + '%');
  }
  sc.addEventListener('scroll', function() {
    if (!raf) raf = requestAnimationFrame(update);
  }, { passive: true });
  update();
})();

Personnaliser

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

Option / propriétéDéfautEffet
Longueur de la course (HTML — .crm-track height: 340%) 340 % (624 px de défilement pour 260 px de fenêtre) p va de 0 à 1 sur (hauteur − 100 %) de la fenêtre : 200% = 260 px, trois crans de molette ; 500% = 1 040 px, tracé lent. C'est aussi le nombre de pixels de molette absorbés avant que la page reprenne la main.
Rayons et longueurs (HTML — r="14"r="58" ; CSS — 88 + var(--i) × 69) 14, 25, 36, 47, 58 / 88 + 69 × i La longueur du trait doit rester 2πr, sinon l'anneau reste ouvert (trop court) ou se referme avant la fin de course (trop long). Pour quatre anneaux de rayon 20, 32, 44, 56 : 126 + var(--i) × 75. Le rayon 58 est le maximum : au-delà, le trait sort du viewBox de 120 et se fait rogner.
Teinte (CSS — oklch(75% .26 calc(280 + var(--i) × 26 + var(--p) × 120))) base 280°, écart 26° par anneau, pivot 120° sur la course Écart 0 = cinq anneaux de même couleur ; pivot 0 = couleur fixe. Chroma .26 sort du gamut sRGB (couleurs écrêtées, différentes sur P3) : .18 donne les mêmes teintes sur tous les écrans. Sur fond clair, clarté 60%.
Rotation, échelle, opacité (CSS — 18deg, .72 + var(--p) × .28, .35 + var(--p) × .65) 18° par anneau, 72 → 100 %, opacité .35 → 1 0deg : les queues restent toutes à midi, tracé strictement concentrique. 1 + var(--p) × 0 : la figure garde 193 px du début à la fin. Opacité 0 + var(--p) × 1 : plus de points à midi au repos, les anneaux naissent en se traçant ; plancher .6 : 3:1 dès le premier quart du tracé.
Paliers successifs (CSS — remplacer var(--p) par clamp(0, var(--p) × 5 − var(--i), 1)) tous ensemble L'anneau i ne se trace qu'entre p = i/5 et (i + 1)/5. Mesuré à p = 0,5 dans les trois moteurs : anneaux 0 et 1 fermés (88 et 157), anneau 2 à mi-course (113 sur 226), 3 et 4 vides. Multipliez p par une fraction d'objectif pour arrêter le remplissage avant le dernier anneau — c'est la variante des trois exemples.
Compteur (JS — Math.round(p × 100) + '%' ; CSS — .crm-pct) 0% → 100%, police héritée Aucune font-family : sur une page vierge, le chiffre s'affiche en Times (mesuré). Posez la police du site sur .crm-pct, et formatez ce que vous voulez dans update() — montant, points, étape — ou masquez-le (display: none).
Lissage entre crans (CSS — à ajouter sur .crm-ring) aucun : chaque cran de molette saute de 16 % transition: stroke-dasharray .3s, transform .3s, stroke .3s, opacity .3s interpole entre deux écritures de --p — mesuré dans les trois moteurs : la longueur passe par des valeurs intermédiaires (55,8 → 233 en 0,8 s) — au prix d'un léger retard sur le doigt.
Instances multiples (JS — getElementById('crmScroll')) une seule (id) Remplacez par document.querySelectorAll('.crm-scroll').forEach(function (sc) { … }) et retirez l'id, ou dupliquez l'IIFE avec un id par scène — c'est ce que font les exemples 2 et 3.

FAQ

Pourquoi cinq points s'affichent-ils au-dessus du centre avant tout défilement ?

Parce qu'à p = 0 le trait mesure 0px mais que stroke-linecap: round ajoute un demi-disque à chaque extrémité : un trait de longueur nulle devient un point du diamètre du trait — 5 unités, soit 6 px à l'échelle de repos (.72) — posé au départ du chemin, ramené à midi par la rotation de −90°. Mesuré à p = 0 : pixel allumé au sommet des cinq cercles, empilés verticalement au-dessus du compteur, à 35 % d'opacité (1,8 à 2,0:1). Ce sont aussi ces bouts ronds qui referment l'anneau extérieur, dont la longueur 364 manque de 0,42 unité. Trois façons de les traiter : les garder comme amorce (ils montrent où le tracé va naître) ; les cacher en passant l'opacité à calc(var(--p) × 1), les anneaux apparaissent alors en se traçant ; ou passer en stroke-linecap: butt, au prix de têtes carrées et d'un anneau extérieur ouvert de 0,7 px.

Concentric Rings Morph ou Corner Morph Overlay (fx-0779) : même script, quelle différence ?

Le moteur est identique — la même IIFE de 19 lignes au préfixe près, la même piste fantôme à 340 % (624 px), la même variable --p ; celui-ci écrit en plus un pourcentage dans data-pct. Tout se joue dans ce que le CSS en fait. fx-0779 dépense p dans une seule propriété, clip-path: circle(), pour révéler un calque qui existe déjà en entier sous le premier : à la fin, rien n'a été dessiné, quelque chose a été découvert, depuis un point qui a un sens (un bouton, un coin). fx-0781 dépense p dans quatre propriétés de cinq <circle> — longueur de trait, rotation, échelle, teinte — pour construire une figure qui n'existe pas au repos : à p = 0 il n'y a que cinq points et « 0% », à p = 1 une cible complète et « 100% ». Le premier raconte un lieu, le second une quantité. Choisissez fx-0781 quand le chiffre compte (couverture, collecte, niveau) et fx-0779 quand l'origine compte. Si c'est un chemin quelconque que vous voulez voir se tracer, Path Drawing on Scroll (fx-0572) dessine un <path> en stroke-dashoffset — mais par animation-timeline, donc pas dans Firefox.

Ma campagne est à 68 % : comment arrêter les anneaux avant le bout, un palier après l'autre ?

Deux changements CSS et une ligne de JS. Remplacez var(--p) dans stroke-dasharray (et dans l'opacité si les anneaux vides doivent rester discrets) par clamp(0, var(--p) × 5 × .68 − var(--i), 1) : l'anneau i se trace entre p = i / 3,4 et (i + 1) / 3,4, et en fin de course les trois premiers anneaux sont fermés, le quatrième à 40 %, le cinquième vide — cinq anneaux, cinq tranches de 20 %. Mesuré avec le facteur 5 seul, à p = 0,5 : 88, 157, 113 (sur 226), 0, 0 dans Chromium, Firefox et WebKit. Pour le compteur, remplacez Math.round(p × 100) par Math.round(p × 68) dans update() — ou par un montant : Math.round(p × 34200).toLocaleString('fr-FR') + ' €'. Ajoutez derrière chaque anneau un cercle statique gris (même r, sans --i) pour montrer les tranches restantes, et reculez le plancher d'opacité à .2 pour que les points de midi des anneaux vides ne prennent pas le dessus.