Aller au contenu
Bibliotheque UIJABS

Mouvement 14 variantes

Le mouvement est le premier marqueur d'un travail soigne, et le premier a mal vieillir quand il est gratuit. Trois regles tenues partout : rien ne bouge sans renseigner sur un changement d'etat, le contenu est lisible AVANT l'animation (on anime l'opacite et la position, jamais l'existence), et prefers-reduced-motion coupe tout sans casser la mise en page. La plupart des variantes reposent sur animation-timeline, natif et sans JavaScript.

A. Revelation au defilement

Le mouvement de base d'une page editoriale : un bloc qui monte a son entree dans le champ. Applique une fois, jamais rejoue.

Faites defiler la zone ci-dessous

Premier blocIl monte de 28 px et se revele
Deuxieme blocChacun se declenche a son entree
Troisieme blocLe mouvement ne se rejoue pas
Quatrieme blocCoupe par prefers-reduced-motion
Revelation simple

Le bloc monte et se revele en entrant dans le champ. C'est le mouvement de base d'une page editoriale : discret, applique une fois, jamais rejoue.

Voir le codeMasquer le code
<div class="mv-scene" data-mv-scene>
  <p class="mv-indice">Faites defiler la zone ci-dessous</p>
  <div class="mv-piste">
    <div class="mv-bloc mv-reveler"><strong>Premier bloc</strong><span>Il monte de 28 px et se revele</span></div>
    <div class="mv-bloc mv-reveler"><strong>Deuxieme bloc</strong><span>Chacun se declenche a son entree</span></div>
    <div class="mv-bloc mv-reveler"><strong>Troisieme bloc</strong><span>Le mouvement ne se rejoue pas</span></div>
    <div class="mv-bloc mv-reveler"><strong>Quatrieme bloc</strong><span>Coupe par prefers-reduced-motion</span></div>
  </div>
</div>
.mv-reveler {
  animation: mv-apparait linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 32%;
}
/* Sans animations pilotees par le defilement, le contenu reste simplement
   visible : jamais d'opacite 0 figee. */
@supports not (animation-timeline: view()) {
  .mv-reveler { animation: none; opacity: 1; transform: none; }
}
animate-[mv-apparait] [animation-timeline:view()] [animation-range:entry_0%_cover_35%]

Faites defiler

01Etude thermique
02Devis et aides
03Pose
04Mise en service
05Entretien
06Garantie 5 ans
Revelation en cascade

Une liste dont les elements arrivent l'un apres l'autre. Le decalage se regle par le point de depart de chaque element, pas par un delai : ainsi le rythme suit la vitesse de defilement.

Voir le codeMasquer le code
<div class="mv-scene" data-mv-scene>
  <p class="mv-indice">Faites defiler</p>
  <div class="mv-piste">
    <div class="mv-grille">
      <div class="mv-carte mv-reveler" style="--i:0"><b>01</b>Etude thermique</div>
      <div class="mv-carte mv-reveler" style="--i:1"><b>02</b>Devis et aides</div>
      <div class="mv-carte mv-reveler" style="--i:2"><b>03</b>Pose</div>
      <div class="mv-carte mv-reveler" style="--i:3"><b>04</b>Mise en service</div>
      <div class="mv-carte mv-reveler" style="--i:4"><b>05</b>Entretien</div>
      <div class="mv-carte mv-reveler" style="--i:5"><b>06</b>Garantie 5 ans</div>
    </div>
  </div>
</div>
.mv-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--sp-3); }
.mv-grille .mv-reveler { animation-range: entry calc(var(--i, 0) * 7%) cover 40%; }
.mv-carte {
  padding: var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
}
.mv-carte b { display: block; margin-bottom: 6px; font-family: var(--font-mono); font-size: 11px; color: var(--c-primary); }
[animation-range:entry_calc(var(--i)*6%)_cover_35%]

Faites defiler

Depuis la gauchemv-glisse-g
Depuis la droitemv-glisse-d
Par l'echellemv-echelle
Par le floumv-flou
Fondu seulmv-fondu, le plus sobre
Directions de revelation

Cinq entrees pour cinq intentions. La direction porte un sens : ce qui vient de la gauche prolonge le texte, ce qui monte est une nouveaute, ce qui se defloute arrive de loin.

Voir le codeMasquer le code
<div class="mv-scene" data-mv-scene>
  <p class="mv-indice">Faites defiler</p>
  <div class="mv-piste">
    <div class="mv-bloc mv-glisse-g"><strong>Depuis la gauche</strong><span>mv-glisse-g</span></div>
    <div class="mv-bloc mv-glisse-d"><strong>Depuis la droite</strong><span>mv-glisse-d</span></div>
    <div class="mv-bloc mv-echelle"><strong>Par l'echelle</strong><span>mv-echelle</span></div>
    <div class="mv-bloc mv-flou"><strong>Par le flou</strong><span>mv-flou</span></div>
    <div class="mv-bloc mv-fondu"><strong>Fondu seul</strong><span>mv-fondu, le plus sobre</span></div>
  </div>
</div>
.mv-glisse-g, .mv-glisse-d, .mv-echelle, .mv-flou, .mv-fondu {
  animation: linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 34%;
}
.mv-glisse-g { animation-name: mv-glisse-g; }
.mv-glisse-d { animation-name: mv-glisse-d; }
.mv-echelle  { animation-name: mv-echelle; }
.mv-flou     { animation-name: mv-flou; }
.mv-fondu    { animation-name: mv-fond; }
@supports not (animation-timeline: view()) {
  .mv-glisse-g, .mv-glisse-d, .mv-echelle, .mv-flou, .mv-fondu {
    animation: none; opacity: 1; transform: none; filter: none;
  }
}
animate-[mv-glisse-g] / [mv-echelle] / [mv-flou] avec la meme timeline

La renovation energetique en 2026

Le remplacement d'une chaudiere fioul par une pompe a chaleur reste l'operation la plus subventionnee. Le montant de MaPrimeRenov depend du revenu fiscal de reference et de la zone climatique.

Les certificats d'economie d'energie s'ajoutent a cette aide et sont verses par le fournisseur d'energie, generalement sous forme de deduction directe sur le devis.

La TVA a taux reduit de 5,5 % s'applique aux travaux d'amelioration de la performance energetique, a condition que l'attestation soit signee avant le debut du chantier.

Le delai moyen constate entre la premiere visite et la mise en service se situe entre trois et six semaines selon la disponibilite du materiel.

Barre de progression de lecture

En tete d'un article long. Elle suit le defilement du document, sans JavaScript, grace a scroll() plutot que view().

Voir le codeMasquer le code
<div class="mv-scene" data-mv-scene>
  <div class="mv-piste mv-piste--article">
    <div class="mv-progres"><i></i></div>
    <h4>La renovation energetique en 2026</h4>
    <p>Le remplacement d'une chaudiere fioul par une pompe a chaleur reste l'operation la plus subventionnee. Le montant de MaPrimeRenov depend du revenu fiscal de reference et de la zone climatique.</p>
    <p>Les certificats d'economie d'energie s'ajoutent a cette aide et sont verses par le fournisseur d'energie, generalement sous forme de deduction directe sur le devis.</p>
    <p>La TVA a taux reduit de 5,5 % s'applique aux travaux d'amelioration de la performance energetique, a condition que l'attestation soit signee avant le debut du chantier.</p>
    <p>Le delai moyen constate entre la premiere visite et la mise en service se situe entre trois et six semaines selon la disponibilite du materiel.</p>
  </div>
</div>
.mv-progres {
  position: sticky;
  top: 0;
  z-index: 2;
  height: 3px;
  background: var(--c-surface-3);
  border-radius: var(--r-full);
  overflow: hidden;
  margin-bottom: var(--sp-4);
}
.mv-progres i {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--c-primary), var(--c-accent));
  animation: mv-remplir linear both;
  animation-timeline: scroll(nearest);
}
@keyframes mv-remplir { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@supports not (animation-timeline: scroll()) { .mv-progres i { animation: none; transform: scaleX(.4); } }
.mv-piste--article h4 { margin: 0 0 var(--sp-3); font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.mv-piste--article p { margin: 0 0 var(--sp-4); font-size: var(--fs-md); line-height: 1.7; color: var(--c-text-soft); max-width: 62ch; }
[animation-timeline:scroll(nearest)] sur une barre en position sticky

B. Defilement mis en scene

Parallaxe, sections qui s'empilent, defilement horizontal, bandeau. A n'employer qu'une fois par page : deux effets de scroll concurrents s'annulent.

Faites defiler

1 240 installationsdepuis 2009 en Ile-de-France

Le fond se deplace de 14 % pendant que le bloc traverse le champ.

Parallaxe

Le fond avance moins vite que le contenu. A doser : au-dela de 15 % de decalage, l'effet se remarque plus que le contenu qu'il sert.

Voir le codeMasquer le code
<div class="mv-scene" data-mv-scene>
  <p class="mv-indice">Faites defiler</p>
  <div class="mv-piste">
    <div class="mv-para">
      <div class="mv-para__fond"></div>
      <div class="mv-para__texte"><strong>1 240 installations</strong><span>depuis 2009 en Ile-de-France</span></div>
    </div>
    <p class="mv-note">Le fond se deplace de 14 % pendant que le bloc traverse le champ.</p>
  </div>
</div>
.mv-para { position: relative; height: 260px; border-radius: var(--r-lg); overflow: hidden; display: grid; place-items: center; }
.mv-para__fond {
  position: absolute;
  inset: -14% 0;
  background:
    radial-gradient(circle at 25% 30%, #4a6fa5 0 22%, transparent 55%),
    radial-gradient(circle at 75% 60%, #a8643c 0 20%, transparent 52%),
    linear-gradient(150deg, #223047, #4a5b73 60%, #6b5a48);
  animation: mv-parallaxe linear both;
  animation-timeline: view();
}
@keyframes mv-parallaxe { from { transform: translateY(-7%); } to { transform: translateY(7%); } }
@supports not (animation-timeline: view()) { .mv-para__fond { animation: none; inset: 0; } }
.mv-para__texte { position: relative; text-align: center; color: #fff; text-shadow: 0 2px 14px rgba(0,0,0,.45); }
.mv-para__texte strong { display: block; font-size: 44px; font-weight: 800; letter-spacing: -.03em; }
.mv-para__texte span { font-size: var(--fs-md); opacity: .88; }
[animation-timeline:view()] sur un pseudo-element de fond

Faites defiler

01Visite technique

Releve sur place, etude thermique offerte.

02Devis et aides

Aides deduites, dossier monte par nos soins.

03Pose

Par nos equipes salariees, en 1 a 3 jours.

04Mise en service

Reglages, prise en main et garantie 5 ans.

Sections empilees

Chaque section se colle en haut pendant que la suivante monte par-dessus. Sert a raconter une progression sans que l'utilisateur perde le fil.

Voir le codeMasquer le code
<div class="mv-scene mv-scene--haute" data-mv-scene>
  <p class="mv-indice">Faites defiler</p>
  <div class="mv-piste mv-empile">
    <section style="--t:#1f3a5f"><b>01</b><strong>Visite technique</strong><p>Releve sur place, etude thermique offerte.</p></section>
    <section style="--t:#2f6b4f"><b>02</b><strong>Devis et aides</strong><p>Aides deduites, dossier monte par nos soins.</p></section>
    <section style="--t:#7a4b2a"><b>03</b><strong>Pose</strong><p>Par nos equipes salariees, en 1 a 3 jours.</p></section>
    <section style="--t:#4a2f5e"><b>04</b><strong>Mise en service</strong><p>Reglages, prise en main et garantie 5 ans.</p></section>
  </div>
</div>
.mv-empile section {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 210px;
  padding: var(--sp-8);
  border-radius: var(--r-lg);
  background: var(--t);
  color: #fff;
  box-shadow: 0 -8px 30px rgba(0,0,0,.18);
}
.mv-empile section + section { margin-top: var(--sp-4); }
.mv-empile b { font-family: var(--font-mono); font-size: 12px; opacity: .6; }
.mv-empile strong { font-size: 28px; font-weight: 800; letter-spacing: -.025em; margin: 6px 0 4px; }
.mv-empile p { margin: 0; font-size: var(--fs-md); opacity: .82; max-width: 46ch; }
sticky top-0 avec un fond opaque par section

Faites defiler

2009Creation de l'atelier
2015Certification RGE QualiPAC
2019Deuxieme equipe de pose
20231 000e installation
2026Ouverture du depot de Bondy
Defilement horizontal

Une section qui se parcourt de gauche a droite pendant que la page descend. Efficace pour une frise ou un portfolio ; a n'utiliser qu'une fois par page.

Voir le codeMasquer le code
<div class="mv-scene mv-scene--haute" data-mv-scene>
  <p class="mv-indice">Faites defiler</p>
  <div class="mv-piste">
    <div class="mv-horiz">
      <div class="mv-horiz__colle">
        <div class="mv-horiz__rail">
          <article style="--t:#22384f"><b>2009</b><span>Creation de l'atelier</span></article>
          <article style="--t:#2f5a45"><b>2015</b><span>Certification RGE QualiPAC</span></article>
          <article style="--t:#6b4a2a"><b>2019</b><span>Deuxieme equipe de pose</span></article>
          <article style="--t:#4a3560"><b>2023</b><span>1 000e installation</span></article>
          <article style="--t:#5e3040"><b>2026</b><span>Ouverture du depot de Bondy</span></article>
        </div>
      </div>
    </div>
  </div>
</div>
.mv-horiz { height: 900px; }
.mv-horiz__colle { position: sticky; top: 0; height: 260px; overflow: hidden; border-radius: var(--r-lg); display: flex; align-items: center; }
.mv-horiz__rail {
  display: flex;
  gap: var(--sp-4);
  padding: 0 var(--sp-4);
  animation: mv-defile linear both;
  animation-timeline: view();
  animation-range: contain 0% contain 100%;
}
@keyframes mv-defile { from { transform: translateX(4%); } to { transform: translateX(-46%); } }
@supports not (animation-timeline: view()) {
  .mv-horiz__rail { animation: none; overflow-x: auto; }
  .mv-horiz { height: auto; }
  .mv-horiz__colle { position: static; height: auto; }
}
.mv-horiz__rail article {
  flex: none;
  width: 260px;
  height: 200px;
  padding: var(--sp-5);
  border-radius: var(--r-lg);
  background: var(--t);
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.mv-horiz__rail b { font-size: 34px; font-weight: 800; letter-spacing: -.03em; }
.mv-horiz__rail span { font-size: var(--fs-sm); opacity: .82; margin-top: 4px; }
sticky + translateX pilote par animation-timeline:view()
RGE QualiPAC•QualiBois•Garantie 5 ans• Devis sous 24 h•1 240 installations•Equipes salariees•
Bandeau defilant

Logos de partenaires, mots-cles, certifications. Le contenu est duplique pour que la boucle soit invisible, et l'animation se met en pause au survol.

Voir le codeMasquer le code
<div class="mv-bandeau">
  <div class="mv-bandeau__rail">
    <span>RGE QualiPAC</span><i>&bull;</i><span>QualiBois</span><i>&bull;</i><span>Garantie 5 ans</span><i>&bull;</i>
    <span>Devis sous 24 h</span><i>&bull;</i><span>1 240 installations</span><i>&bull;</i><span>Equipes salariees</span><i>&bull;</i>
    <span aria-hidden="true">RGE QualiPAC</span><i aria-hidden="true">&bull;</i><span aria-hidden="true">QualiBois</span><i aria-hidden="true">&bull;</i>
    <span aria-hidden="true">Garantie 5 ans</span><i aria-hidden="true">&bull;</i><span aria-hidden="true">Devis sous 24 h</span><i aria-hidden="true">&bull;</i>
    <span aria-hidden="true">1 240 installations</span><i aria-hidden="true">&bull;</i><span aria-hidden="true">Equipes salariees</span><i aria-hidden="true">&bull;</i>
  </div>
</div>
.mv-bandeau {
  width: 100%;
  padding: var(--sp-4) 0;
  border-top: 1px solid var(--c-border);
  border-bottom: 1px solid var(--c-border);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.mv-bandeau__rail {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  width: max-content;
  animation: mv-bandeau 32s linear infinite;
}
.mv-bandeau:hover .mv-bandeau__rail { animation-play-state: paused; }
@keyframes mv-bandeau { to { transform: translateX(-50%); } }
.mv-bandeau span { font-size: var(--fs-lg); font-weight: var(--fw-semi); white-space: nowrap; color: var(--c-text); }
.mv-bandeau i { color: var(--c-primary); font-style: normal; }
animate-[marquee_28s_linear_infinite] hover:[animation-play-state:paused]

C. Mouvements d'interface

Transition entre pages, compteur, texte revele mot par mot, bouton magnetique, curseur suiveur, chiffres qui roulent.

/* Dans le CSS du site, une seule fois */
@view-transition { navigation: auto; }

/* Regler la duree et la courbe */
::view-transition-old(root) { animation: 180ms ease-out both mv-sortie; }
::view-transition-new(root) { animation: 260ms cubic-bezier(.2,0,0,1) both mv-entree; }

@keyframes mv-sortie { to   { opacity: 0; transform: translateY(-8px); } }
@keyframes mv-entree { from { opacity: 0; transform: translateY(12px); } }

/* Un element partage entre les deux pages garde son identite :
   la vignette d'une liste devient l'image de la fiche, sans coupure. */
.vignette-103 { view-transition-name: photo-103; }

Aucun JavaScript, aucune bibliotheque. C'est ce qui remplace aujourd'hui les transitions de page qui demandaient un routeur complet.

Transition entre pages

La View Transitions API fait glisser une page vers la suivante au lieu du clignotement blanc. Deux lignes de CSS suffisent, et les navigateurs qui l'ignorent affichent la navigation habituelle.

Voir le codeMasquer le code
<div class="mv-code">
  <pre><code>/* Dans le CSS du site, une seule fois */
@view-transition { navigation: auto; }

/* Regler la duree et la courbe */
::view-transition-old(root) { animation: 180ms ease-out both mv-sortie; }
::view-transition-new(root) { animation: 260ms cubic-bezier(.2,0,0,1) both mv-entree; }

@keyframes mv-sortie { to   { opacity: 0; transform: translateY(-8px); } }
@keyframes mv-entree { from { opacity: 0; transform: translateY(12px); } }

/* Un element partage entre les deux pages garde son identite :
   la vignette d'une liste devient l'image de la fiche, sans coupure. */
.vignette-103 { view-transition-name: photo-103; }</code></pre>
  <p class="mv-note">Aucun JavaScript, aucune bibliotheque. C'est ce qui remplace aujourd'hui les transitions de page qui demandaient un routeur complet.</p>
</div>
.mv-code pre { margin: 0; padding: var(--sp-4); border-radius: var(--r-md); background: #12151b; color: #d5dde8; font-family: var(--font-mono); font-size: 12px; line-height: 1.7; overflow-x: auto; }
.mv-note { margin: var(--sp-3) 0 0; font-size: var(--fs-sm); color: var(--c-text-muted); line-height: 1.55; max-width: 70ch; }
@view-transition { navigation: auto } + ::view-transition-old/new
1 240installations depuis 2009
4,8note moyenne sur 312 avis
98pour cent de chantiers dans les delais
Compteur qui s'incremente

Un chiffre de preuve qui monte a l'arrivee dans le champ. Le nombre final est ecrit dans le balisage : si le script ne s'execute pas, la valeur reste juste.

Voir le codeMasquer le code
<div class="mv-chiffres">
  <div><strong data-mv-compte="1240">1 240</strong><span>installations depuis 2009</span></div>
  <div><strong data-mv-compte="4.8" data-mv-dec="1">4,8</strong><span>note moyenne sur 312 avis</span></div>
  <div><strong data-mv-compte="98">98</strong><span>pour cent de chantiers dans les delais</span></div>
</div>
.mv-chiffres { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--sp-5); width: 100%; text-align: center; }
.mv-chiffres strong { display: block; font-size: 46px; font-weight: 800; letter-spacing: -.035em; color: var(--c-text); font-variant-numeric: tabular-nums; line-height: 1; }
.mv-chiffres span { display: block; margin-top: 8px; font-size: var(--fs-sm); color: var(--c-text-muted); line-height: 1.45; }
JS : IntersectionObserver + requestAnimationFrame (voir js/motion.js)

Nous posons des pompes a chaleur depuis 2009, avec nos propres equipes.

Texte revele mot par mot

Une phrase d'ouverture qui se construit. Chaque mot reste un mot dans le balisage : la selection, la recherche et les lecteurs d'ecran ne voient aucune difference.

Voir le codeMasquer le code
<p class="mv-mots">
  <span>Nous</span> <span>posons</span> <span>des</span> <span>pompes</span> <span>a</span> <span>chaleur</span>
  <span>depuis</span> <span>2009,</span> <span>avec</span> <span>nos</span> <span>propres</span> <span>equipes.</span>
</p>
.mv-mots {
  margin: 0;
  font-size: clamp(22px, 3.4vw, 34px);
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -.02em;
  max-width: 22ch;
}
.mv-mots span {
  display: inline-block;
  animation: mv-mot linear both;
  animation-timeline: view();
  animation-range: entry 10% cover 30%;
}
@keyframes mv-mot { from { opacity: 0; transform: translateY(.4em) rotate(2deg); } to { opacity: 1; transform: none; } }
.mv-mots span:nth-child(n+4) { animation-range: entry 14% cover 34%; }
.mv-mots span:nth-child(n+8) { animation-range: entry 18% cover 38%; }
@supports not (animation-timeline: view()) { .mv-mots span { animation: none; opacity: 1; transform: none; } }
[&>span]:animate-[mv-apparait] [&>span]:[animation-timeline:view()] avec un range decale
Bouton magnetique

Le bouton suit legerement le curseur avant le clic. Reserve a l'action principale d'une banniere : applique partout, l'effet devient un tic.

Voir le codeMasquer le code
<div class="demo-row" style="padding:30px 0">
  <button class="btn btn--plein btn--xl mv-aimant" data-mv-aimant>Demander un devis</button>
  <button class="btn btn--secondaire btn--xl mv-aimant" data-mv-aimant>Nous appeler</button>
</div>
.mv-aimant { transition: transform 320ms cubic-bezier(.2,0,0,1), background-color var(--t-fast); will-change: transform; }
JS : translate calcule depuis la position du curseur (voir js/motion.js)

Deplacez le curseur dans cette zone.

Curseur suiveur

Un rond qui suit le curseur et grossit sur les elements cliquables. Signature d'agence, a n'employer que sur un site vitrine, jamais dans un outil de travail.

Voir le codeMasquer le code
<div class="mv-curseur-zone" data-mv-curseur>
  <div class="mv-curseur" aria-hidden="true"></div>
  <p>Deplacez le curseur dans cette zone.</p>
  <div class="demo-row">
    <a href="#" class="btn btn--secondaire" data-mv-cible>Un lien</a>
    <a href="#" class="btn btn--secondaire" data-mv-cible>Un autre</a>
  </div>
</div>
.mv-curseur-zone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  width: 100%;
  min-height: 220px;
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  overflow: hidden;
}
.mv-curseur-zone p { margin: 0; font-size: var(--fs-sm); color: var(--c-text-muted); }
.mv-curseur {
  position: absolute;
  top: 0; left: 0;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border: 1.5px solid var(--c-primary);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transition: transform 160ms cubic-bezier(.2,0,0,1), opacity 200ms, background-color 200ms;
  z-index: 5;
}
.mv-curseur-zone.is-actif .mv-curseur { opacity: 1; }
.mv-curseur.is-cible { transform: scale(2.1); background: color-mix(in srgb, var(--c-primary) 14%, transparent); }
/* Sur un ecran tactile, il n'y a pas de curseur a suivre. */
@media (hover: none) { .mv-curseur { display: none; } }
JS : position en pointer-events-none, scale au survol des cibles
4280,00 EUR
Chiffres qui roulent

Un montant qui change en direct : compteur de dossiers, total d'un panier. Seul le chiffre modifie bouge, ce qui evite de faire clignoter toute la ligne.

Voir le codeMasquer le code
<div class="mv-roue" data-mv-roue>
  <span>4</span><span>2</span><span>8</span><span>0</span><em>,00 EUR</em>
</div>
.mv-roue { display: inline-flex; align-items: baseline; font-size: 40px; font-weight: 800; letter-spacing: -.03em; font-variant-numeric: tabular-nums; color: var(--c-text); }
.mv-roue span {
  display: inline-block;
  height: 1em;
  overflow: hidden;
  line-height: 1;
}
.mv-roue span::before {
  content: "0\A 1\A 2\A 3\A 4\A 5\A 6\A 7\A 8\A 9";
  display: block;
  white-space: pre;
  transform: translateY(calc(var(--n, 0) * -1em));
  transition: transform 620ms cubic-bezier(.2,0,0,1);
}
.mv-roue em { font-style: normal; font-size: .55em; font-weight: var(--fw-semi); color: var(--c-text-muted); margin-left: 2px; }
chaque chiffre est une colonne de 0 a 9 translatee verticalement