Formulaires✨ Premium

Pricing Toggle Odometer

Bascule mensuel / annuel pour une carte de prix : chaque chiffre roule dans sa colonne comme un compteur, le badge d'économie et le montant annuel apparaissent.

Vanilla JSOdometerPricing

Mis à jour le

Cet effet fait partie d'Effect.Labs — 840 effets vanilla, une partie gratuite, une partie premium. Formulaires comprend 20 effets, dont 2 gratuits. Découvrir la catégorie →

3 exemples d'usage

Page tarifs d'un club de lecture — deux formules, une bascule par carte — Pricing Toggle Odometer exemple 1

① Page tarifs d'un club de lecture — deux formules, une bascule par carte

QuandLe visiteur compare les formules Lecteur et Cercle. Il passe le Cercle en annuel : 19 roule vers 15, « −21 % » apparaît et « Facturé 180 € / an » s'affiche, pendant que Lecteur reste au mois tant qu'on ne touche pas à sa bascule.
PourquoiUne bascule par carte laisse comparer un forfait au mois avec l'autre à l'année. Le roulement attire l'œil sur le chiffre qui change, sans changer de page ni recharger la grille.
RéglagesDeux cartes dans une seule racine (display:flex, --pto-w:auto pour qu'elles se partagent la largeur). Lecteur : 9 / 7 € ; Cercle : 19 / 15 €. Couleurs du rail et du bouton reprises dans la charte terre cuite du club. Dans la capture, les clics hors des bascules sont relayés à la bascule du Cercle puis à celle de Lecteur.
Bandeau « passez à la facture annuelle » dans un espace client — Pricing Toggle Odometer exemple 2

② Bandeau « passez à la facture annuelle » dans un espace client

QuandUn abonné ouvre son espace de notes. Un bandeau en haut lui propose l'annuel : il actionne la bascule et voit tout de suite son prix passer de 8 à 6 € par mois, avec « −25 % » et le montant facturé à l'année.
PourquoiLe client se décide sur un chiffre, pas sur une promesse : il voit ce qu'il paierait avant de cliquer sur quoi que ce soit d'engageant. Le bandeau reste d'une seule hauteur, l'espace client garde sa liste de notes visible.
RéglagesLe conteneur [data-pto] n'est pas une .pto-card : une grille de deux lignes (texte et bascule à gauche, prix à droite). Prix en 34 px (font-size de .pto-amount), 8 / 6 €.
Bandeau d'offre d'un abonnement café — le prix bascule tout seul — Pricing Toggle Odometer exemple 3

③ Bandeau d'offre d'un abonnement café — le prix bascule tout seul

QuandEn haut de la page d'une offre limitée, un prix géant alterne entre 24 € au mois et 18 € à l'année, avec le badge « −25 % », toutes les 1,6 s ; le visiteur peut aussi prendre la main sur la bascule en bas du bandeau.
PourquoiLe roulement répété fait lire les deux prix sans demander d'action : l'écart devient l'accroche du bandeau, à côté du compte à rebours de l'offre.
RéglagesPrix agrandi à 112 px (les colonnes sont en em, elles suivent). C'est la scène qui coche et décoche la case toutes les 1,6 s et envoie l'événement change que le code écoute : le code vendu ne bascule jamais seul.

Comment ça marche

Chaque carte [data-pto] placée dans .fx-0808-root porte ses deux prix entiers : data-monthly="19" et data-yearly="15". Au chargement, le script efface le prix écrit en clair dans [data-pto-digits] (celui qu'on lit sans JavaScript) et le remplace par une colonne par chiffre, chacune contenant les chiffres 0 à 9 empilés. Afficher « 15 », c'est décaler la première colonne de 1 em vers le haut et la seconde de 5 em : la transition transform .65s cubic-bezier(.2,.85,.25,1) fait rouler les chiffres.

La bascule est une vraie case à cocher, invisible, posée dans un <label> avec le rail .pto-track. À l'événement change, le script choisit le prix annuel ou mensuel, met « Mensuel » ou « Annuel » en gras selon l'état, montre le badge .pto-save et la ligne .pto-sub (« Facturé 180 € / an », soit le prix annuel × 12, calculé au chargement). Le badge vaut data-save s'il est fourni, sinon l'économie réelle arrondie : 19 → 15 donne −21 %.

Chaque carte a sa propre bascule et ses propres colonnes : trois cartes côte à côte ou deux copies du bloc fonctionnent sans se gêner, et coller le script deux fois ne reconstruit pas les colonnes. Quand un prix compte moins de chiffres que l'autre (9 contre 12), le zéro de tête est masqué. Dans un <form>, un reset décoche la case et remet aussitôt le prix mensuel. Prix entiers uniquement : parseInt ignore les centimes.

Accessibilité

  • Prix lu en clair : les colonnes 0-9 sont masquées aux lecteurs d'écran (aria-hidden) ; une phrase invisible « 19 € par mois », mise à jour à chaque bascule, est annoncée grâce à aria-live="polite".
  • Clavier : la case reste atteignable par Tab et se coche avec Espace ; un anneau clair se dessine autour du rail au focus clavier (:focus-visible). Son nom accessible est « Facturation annuelle ».
  • Mouvement réduit : sous prefers-reduced-motion: reduce, les chiffres sont posés directement à leur valeur, sans défilement, et le rail comme le badge changent sans transition. Le prix change quand même.
  • Sans JavaScript : le prix mensuel écrit dans le HTML reste affiché ; la bascule se coche mais ne change rien.

Compatibilité navigateur

JavaScript sans dépendance (padStart, matchMedia, classList) et CSS courant. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 (Playwright) : deux copies dans la même page, clic sur la seconde sans effet sur la première, Tab puis Espace, toucher à 390 px, mouvement réduit, 0 erreur. L'anneau de focus utilise :focus-visible (Chrome 86, Firefox 85, Safari 15.4) ; avant, la bascule marche au clavier sans anneau visible.

Chrome 86+✓ Complet (mesuré)
Firefox 85+✓ Complet (mesuré)
Safari 15.4+✓ Complet (mesuré dans WebKit)
Edge 86+✓ Complet (moteur Chromium)
Mobile iOS 15.4+✓ Moteur WebKit, non vérifié sur appareil
Android Chrome 86+✓ Complet (toucher mesuré à 390 px)

Sans JavaScript, le prix mensuel du HTML reste lisible. Sans :focus-visible (Safari avant 15.4), pas d'anneau au focus clavier mais la bascule reste utilisable.

Le code

Structure HTML à coller dans votre page (le CSS + JS sont inclus avec votre achat) :

index.html — structure
<div class="fx-0808-root">
  <div class="pto-scene">
    <div class="pto-card" data-pto data-monthly="19" data-yearly="15">
      <label class="pto-switch">
        <span class="pto-m pto-on">Mensuel</span>
        <input type="checkbox" data-pto-toggle aria-label="Facturation annuelle">
        <span class="pto-track" aria-hidden="true"></span>
        <span class="pto-y">Annuel</span>
      </label>
      <div class="pto-price">
        <span class="pto-amount" aria-hidden="true"><span data-pto-digits>19</span><span class="pto-cur">€</span></span>
        <span class="pto-suffix" aria-hidden="true">/ mois</span>
        <span class="pto-save" data-pto-save>−<span data-pto-pct>21</span> %</span>
        <span class="pto-vh" aria-live="polite" aria-atomic="true"><span data-pto-sr>19</span> € par mois</span>
      </div>
      <div class="pto-sub" data-pto-sub>Facturé <span data-pto-total>180</span> € / an</div>
      <button type="button" class="pto-cta">Commencer</button>
    </div>
  </div>
</div>

Paiement unique, sans abonnement — le code est à toi pour toujours

Personnaliser

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

Option / propriétéDéfautEffet
Prix (HTML — data-monthly et data-yearly sur la carte) 19 et 15 Nombres entiers. Écrivez aussi le prix mensuel dans [data-pto-digits] : c'est ce qui s'affiche sans JavaScript.
Économie du badge (HTML — data-save sur la carte) absent : calculée (−21 % pour 19 → 15) Donnez un nombre pour imposer le pourcentage affiché, par exemple data-save="20".
Largeur de la carte (CSS — --pto-w) 280px auto laisse la carte prendre la largeur de sa colonne (grille de formules).
Taille du prix (CSS — font-size de .pto-amount) 52px Les colonnes de chiffres sont en em : elles suivent la taille, de 30 px dans un bandeau à 120 px dans un visuel d'accueil.
Vitesse du roulement (CSS — transition de .pto-strip) .65s cubic-bezier(.2,.85,.25,1) Raccourcissez à .4s pour un compteur plus sec.
Couleur de la bascule (CSS — .pto-switch input:checked+.pto-track) #6366f1 Le rail coché prend cette couleur ; l'anneau de focus utilise #a5b4fc.
Textes et devise (HTML) Mensuel, Annuel, €, / mois, Facturé … € / an Tout le texte est dans le HTML : placez .pto-cur avant ou après [data-pto-digits], traduisez la phrase lue par les lecteurs d'écran (.pto-vh) et le nom de la case (aria-label).

FAQ

Non, le script lit des entiers. Pour 9,99 €, affichez 10 dans le compteur ou ajoutez les centimes en texte fixe à côté de .pto-amount ; seule la partie entière roulera.
Pas tel quel : chaque carte [data-pto] écoute sa propre case. Pour une bascule commune, cachez celles des cartes et, au changement de votre bascule, cochez chaque case puis envoyez-lui dispatchEvent(new Event('change')).
Leur système demande moins de mouvement (prefers-reduced-motion) : le script pose alors les chiffres directement. Le prix, le badge et la ligne annuelle changent exactement de la même façon.