ScrollGratuit

Progress Bar

Une piste de 4 px collée en tête d'un cadre de 280 px par position: sticky, dont le remplissage suit le défilement du cadre en scaleX(0 → 1) grâce à animation-timeline: scroll(nearest) — zéro JavaScript, zéro id, autant de copies que de cadres. Mesuré : scaleX = scrollTop ÷ 524 au millième, 0,5 à 262 px, 1 au dernier pixel ; la page qui défile autour ne bouge pas la barre.

CSSScrollProgress

Mis à jour le

Barre de progression

Scrollez pour voir la barre se remplir. Utilise animation-timeline: scroll() pour lier l'animation au défilement du conteneur.

Scrollez ↓
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

TORNEX Configurateur
Étape 2 sur 4 · Options

Fiche technique — TX-420

Mise à jour 09/2026
Puissance
Puissance broche22 kW
Vitesse maximale4 500 tr/min
Couple maximal320 N·m
Alimentation400 V · 50 Hz
Courses
Course X320 mm
Course Z750 mm
Diamètre de tournage max.420 mm
Longueur de tournage max.700 mm
Avance rapide X/Z30 / 36 m/min
Précision
Précision de positionnement±0,005 mm
Répétabilité±0,003 mm
Faux-rond broche0,002 mm
Tourelle
Postes d'outils12
Temps d'indexage0,15 s
Outils motorisés12 × 6 000 tr/min
Encombrement
Dimensions L × P × H3 250 × 1 900 × 2 050 mm
Masse4 800 kg
Commande numériqueFanuc 0i-TF Plus

① Fiche technique d'une machine-outil — volet documentation d'un configurateur industriel

QuandConfigurateur d'un tour à commande numérique : à gauche les options cochées (broche, contre-pointe, convoyeur), à droite un volet de hauteur fixe qui contient la fiche technique — puissance, courses, tolérances, encombrement — avec la barre en tête.
PourquoiLe volet est un scroller imbriqué dans une page qui défile elle-même : scroll(nearest) prend le volet, pas la page — mesuré, la page défilée de 2 500 px laisse la barre à 0, la molette dans le volet la remplit. Scroll Progress Beam (fx-0793, publié) réclame un rail vertical en marge que ce volet étroit n'a pas ; la piste de 4 px collée en tête ne prend aucune place.
RéglagesCadre de 262 px (height: 262px sur .sdc-scroll-container, 260 px utiles avec la bordure), contenu réel à height: auto (course mesurée 399 px), piste blanc à 7 %, remplissage linear-gradient(90deg, #22d3ee, #0ea5e9), tabindex="0" et role="region" sur le cadre. Le volet avance seul jusqu'au premier geste — un artifice de démonstration, pas une capacité de l'effet.
Étagère HALDE · chêne clair
80 × 30 × 180 cm · 5 tablettes · livrée en kit
129 €Ajouter au panier

Notice de montage

9 étapes · 35 min
Déballer et vérifier

2 montants, 5 tablettes, 1 fond, 24 vis, 8 chevilles, 1 clé.

Poser les chevilles

Enfoncez les 8 chevilles dans les montants, côté intérieur.

Fixer la tablette du bas

Quatre vis de 40 mm dans les montants, sans forcer.

Fixer la tablette du haut

Même geste ; le cadre tient maintenant debout.

Placer les tablettes intermédiaires

Trois tablettes posées sur les chevilles, à 40 cm d'écart.

Clouer le fond

Meuble à plat, panneau aligné, un clou tous les 15 cm.

Vérifier l'équerrage

Mesurez les deux diagonales : elles doivent être égales.

Poser les patins

Quatre patins feutre sous les montants.

Fixer au mur

Équerre anti-basculement et cheville adaptée au mur.

② Notice de montage d'une étagère en kit — panneau latéral d'une boutique de mobilier

QuandFiche produit d'une étagère livrée en kit : à gauche le visuel, le prix et le bouton d'achat, immobiles ; à droite un panneau à hauteur fixe qui fait défiler les 9 étapes de montage, la barre en tête du panneau.
PourquoiLa barre est dans le panneau, collée en tête, et scroll(nearest) l'attache au plus proche ancêtre qui défile : le panneau, pas la page. Elle ne dit qu'une chose, où l'on en est de la notice — mesuré, course de 390 px, 0,5 quand l'étape 4 arrive sous la piste, 1 sur la dernière ligne de l'étape 9 — pendant que le visuel, le prix et le bouton ne bougent pas ; le client qui monte le meuble, téléphone posé sur le carton, voit ce qu'il lui reste sans compter les étapes. Si la fiche défile à son tour, la barre n'en tient pas compte : mesuré, page défilée de 900 puis 1 500 px, barre à 0 ; panneau à mi-course puis page ramenée de 400 px, barre toujours à 0,5. Elle doit rester dans le panneau : posée dans l'en-tête de la fiche, nearest remonterait au défileur suivant, la page.
RéglagesFond clair : panneau en flex: 1 à droite du visuel, cadre en height: auto (259 px mesurés) avec contenu réel (course mesurée 390 px), piste #ece5dc, remplissage uni #a0623a, étapes en grille 22px 1fr avec le numéro dans une pastille, tabindex="0", role="region" et aria-label sur le cadre. Le panneau avance seul jusqu'au premier geste — un artifice de démonstration.
Atelier Sonore · Saison 3
Épisode 47 — Réparer plutôt que remplacer
12:4841:20

Transcription

41 min · 6 200 mots

Léa (hôte) — Bienvenue dans Atelier Sonore. Aujourd'hui, on parle de ce qui casse, et de ce qu'on en fait : un grille-pain, un téléphone, une machine à laver.

Marc — Il y a dix ans, j'ai ouvert un atelier de réparation dans une ancienne quincaillerie. On m'a dit que ça ne tiendrait pas six mois.

Léa — Qu'est-ce qui arrive le plus souvent sur ton établi ?

Marc — Des aspirateurs et des cafetières. Dans huit cas sur dix, c'est un fusible thermique à deux euros ou un joint. Le reste du temps, c'est la carte.

Léa — Et l'indice de réparabilité, ça change quelque chose ?

Marc — Pour les clients, oui : ils regardent la note avant d'acheter. Pour nous, ce qui compte, c'est la pièce détachée disponible cinq ans après, et le schéma.

Léa — Tu formes aussi des gens ?

Marc — Un samedi par mois. Les gens viennent avec leur objet, on l'ouvre ensemble. La plupart repartent avec l'objet réparé et l'envie de recommencer.

Léa — On fait une courte pause, et on revient avec les questions des auditeurs.

Léa — Première question, de Nadia à Lyon : est-ce que ça vaut le coup de réparer un téléphone dont la batterie est collée ?

Marc — Oui, avec une plaque chauffante et de la patience. La colle ramollit à soixante degrés ; la batterie coûte vingt euros. L'heure de main-d'œuvre, c'est le vrai prix.

Léa — Tom, à Nantes, demande quels outils acheter en premier.

Marc — Un multimètre, un jeu de tournevis de précision, et une lampe. Le reste vient avec les objets qu'on ouvre.

Léa — Et les objets que tu refuses de réparer ?

Marc — Tout ce qui a une cellule lithium fendue, et tout ce qui n'a ni schéma ni pièce. Je le dis dès le premier jour : je préfère perdre un client que vendre une réparation qui ne tiendra pas.

③ Transcription d'un épisode de podcast — page d'écoute d'un studio audio

QuandPage épisode : le lecteur en haut avec sa pochette, son bouton et sa propre barre de temps orange ; dessous, la transcription horodatée dans un cadre de 280 px, barre de progression de lecture en tête du cadre.
PourquoiLa page a déjà un défilement principal et le lecteur sa barre de temps orange : la barre verte en tête du cadre ne lit que le défilement de la transcription — mesuré, la page défilée laisse la barre à 0 — et l'auditeur voit d'un coup d'œil où il en est du texte par rapport à l'audio (12:48 sur 41:20), qu'il lise en avance ou en retard. Le texte, lui, ne bouge pas : seule la ligne de 4 px avance. Progress Reveal (fx-0588) ferait entrer chaque réplique en animation sous la même piste — une transcription qu'on parcourt en écoutant doit rester immobile, pas s'animer.
RéglagesCadre de 280 px exactement (position: absolute; top: 80px sous le lecteur), remplissage linear-gradient(90deg, #34d399, #10b981) — vert pour ne pas se confondre avec l'orange du lecteur —, piste blanc à 8 %, répliques en grille 44px 1fr avec l'horodatage en chasse fixe, role="region" et aria-label sur le cadre ; course mesurée 504 px.

Comment ça marche

Le HTML vendu tient en trois niveaux : le cadre .demo-preview (flex centré, 100% × 280px, overflow: hidden, position: relative, fond #0a0a0f, texte blanc, sans-serif, et la règle box-sizing: border-box pour tout ce qu'il contient), le bloc défilant .sdc-scroll-container (100% × 280px, overflow-y: auto, scroll-behavior: smooth, barre de défilement de 4 px stylée par ::-webkit-scrollbar) et, dedans, dans l'ordre du flux, la piste .sdc-progress-bar (position: sticky; top: 0, 100% × 4px, fond blanc à 10 %, z-index: 10) qui contient le remplissage .sdc-progress-fill (hauteur 100 %, dégradé 90° #8b5cf6 → #ec4899, transform-origin: left, transform: scaleX(0)), puis le contenu .sdc-progress-content : padding: 20px, 800 px de haut, colonne flex à gap: 16px, un h3 de 1 rem graisse 700, un p de 0,78 rem en blanc à 50 % (interligne 1,6) et quatre .sdc-progress-block de 120 px (fond blanc à 4 %, bord blanc à 8 %, rayon 12 px). Sous le cadre, un span.sdc-scroll-hint « Scrollez ↓ » en absolu à 8 px du bas, 0,65 rem, blanc à 30 %, qui pulse jusqu'à 70 % toutes les 2 s. Le champ JavaScript est vide. Mesuré : scrollHeight 804 (800 px de contenu + 4 px de piste, qui est dans le flux), course 524 px.

Le moteur tient en deux déclarations sur .sdc-progress-fill : animation: sdcProgressGrow linear et animation-timeline: scroll(nearest). scroll() remplace l'horloge de l'animation par une timeline de progression de défilement anonyme : 0 % quand le défileur est en haut, 100 % à scrollHeight − clientHeight ; nearest (la valeur par défaut, écrite ici en toutes lettres) désigne le plus proche ancêtre qui défile — .sdc-scroll-container, pas la page — sur l'axe du bloc. Les keyframes vont de scaleX(0) à scaleX(1) ; linear n'est pas un détail : toute autre courbe déforme la proportion (mesuré avec ease-out : 0,68 à mi-course). Mesuré dans Chromium, page vierge, molette par crans de 130 px : 0,248 à 130 px, 0,496 à 260, 0,744 à 390, 0,992 à 520, 1 à 524 — scaleX = scrollTop ÷ 524 au millième ; un scrollTop = 262 posé par script donne 0,5 exactement. getAnimations() renvoie une animation sur une ScrollTimeline dont currentTime est un pourcentage (24,8 à 130 px), finished au bout, running à nouveau dès qu'on remonte. La piste collée ne bouge pas d'un pixel pendant que le titre passe dessous (mesuré : piste à y = 0 du cadre sur toute la course, h3 à −98 puis −492 px). Comme c'est un transform et non une largeur, le dégradé est peint sur toute la largeur puis comprimé : à 50 %, on voit tout le dégradé violet → rose sur la moitié gauche, et la pointe est toujours rose.

Le keyframe from { transform: scaleX(0) } est écrit explicitement, et ce n'est pas redondant. Sans lui, le point de départ est la valeur sous-jacente de l'élément, scaleX(0), une matrice singulière : Chromium interpole quand même les fonctions terme à terme (0,25 / 0,5 / 0,75 aux quarts), mais WebKit 26.5, mesuré, retourne 0,0625 / 0,25 / 0,5625 — le carré de la progression, une barre au quart quand la lecture est à moitié — alors que sa timeline est juste (currentTime 25, 50, 75 %). Avec le from, WebKit rend 0,25 / 0,5 / 0,75 / 1 comme Chromium. L'autre point à connaître est ce que nearest choisit vraiment. Mesuré, effet posé entre 2 000 px de contenu avant et après : la page défilée de 1 900 puis 2 500 px laisse la barre à 0 ; une molette de 262 px sur le cadre la met à 0,5, et la page ne bouge pas. Un second exemplaire dont le contenu ne fait que 200 px, sans débordement : la timeline lui est bien attachée mais reste inactive (currentTime nul), barre vide — elle ne remonte pas à la page. Deux copies sur la même page : aucun id, aucun script, chaque barre ne lit que son cadre. Le scroll-behavior: smooth n'agit pas sur la molette dans Chromium ; un scrollTop = 262 posé par script se relit 0 tout de suite et arrive en 249 ms (493 ms dans Firefox).

Accessibilité

  • prefers-reduced-motion absent du code : mesuré sous émulation, rien ne change — la barre suit le défilement (0,248 à 130 px), l'indice pulse (sdcHintPulse running) et le smooth reste. Rien n'est caché : le remplissage ne bouge que du geste du lecteur, à la proportion exacte, ce qui en fait un indicateur et non une animation autonome (WCAG 2.3.3 vise le mouvement non essentiel). Ajoutez tout de même @media (prefers-reduced-motion: reduce) { .sdc-scroll-hint { animation: none } .sdc-scroll-container { scroll-behavior: auto } } pour la pulsation et les défilements programmés.
  • Clavier : mesuré, Tab donne le focus au cadre sans tabindex dans Chromium et Firefox (activeElement = .sdc-scroll-container) ; flèche bas avance de 40 px (barre à 7,6 %), Page suivante de 285 px (54 %), Fin va au bout (100 %). WebKit ne focalise pas un bloc défilant par Tab (mesuré : focus resté sur body, flèche bas sans effet) : ajoutez tabindex="0", un role="region" avec aria-label, et un style :focus-visible — l'anneau par défaut entoure tout le cadre.
  • Contraste (sur #0a0a0f) : titre blanc 19,8:1 ; paragraphe blanc à 50 % 5,4:1 à 12,5 px (seuil 4,5:1 tenu) ; remplissage 4,7:1 (violet) à 5,6:1 (rose) sur le fond et 3,7 / 4,4:1 sur la piste — au-dessus des 3:1 demandés à un composant graphique. La piste vide, blanc à 10 %, vaut 1,26:1 : à peine visible — c'est exactement ce que voit un visiteur Firefox. L'indice à 30 % vaut 2,6:1 au repos, de 1,2:1 à 1,8:1 sous la pulsation (la couleur à 30 % et l'opacité de 0,3 à 0,7 se multiplient) : posez aria-hidden="true" dessus ou supprimez le span, il est décoratif et son texte est en dur.
  • Lecteurs d'écran : la piste et son remplissage sont deux div vides, rien n'est annoncé, et la valeur n'existe que dans le compositeur — aucune règle CSS ne peut l'écrire dans un attribut. Si la progression a un sens (règlement à lire jusqu'au bout, attestation), il faut un role="progressbar" dont aria-valuenow est mis à jour par un écouteur scroll de quatre lignes (c'est ce que fait Introduction, fx-0575) ; sinon aria-hidden="true" sur .sdc-progress-bar. Au doigt, le défilement est natif (aucun écouteur) ; overscroll-behavior: contain évite d'entraîner la page en bout de cadre.

Compatibilité navigateur

CSS seul : animation-timeline: scroll() est la seule propriété qui fixe le plancher — Chrome et Edge 115, Safari 26 et iOS 26, Firefox aucune version (mesuré dans Firefox 153 : CSS.supports('animation-timeline: scroll()') faux, getAnimations() vide). Le reste est classique : position: sticky, transform, linear-gradient, @keyframes, overflow-y ; scroll-behavior et ::-webkit-scrollbar sont cosmétiques. Zéro dépendance, champ JavaScript vide. Le from explicite des keyframes est exigé par WebKit (voir la mécanique) : sans lui, Safari 26 rend le carré de la progression.

Chrome 115+✓ Complet
Firefox✓ Aucune version (piste vide, contenu lisible)
Safari 26+✓ Complet
Edge 115+✓ Complet
Mobile iOS 26+✓ Complet
Android Chrome 115+✓ Complet

Mesuré dans Firefox, page vierge, molette par crans de 130 px puis scrollTop posé à 50 et 100 % : le remplissage reste à scaleX(0) du début à la fin — la piste de 4 px (blanc à 10 %, 1,26:1) est vide et presque invisible, tout le reste fonctionne. Rien n'est caché, rien ne se superpose : le visiteur Firefox n'a simplement pas de barre. Même chose avant Chrome 115 et Safari 26. Le code vendu ne contient pas de repli @supports ; deux choix : masquer la piste là où la timeline manque — @supports not (animation-timeline: scroll()) { .sdc-progress-bar { display: none } } — ou prendre le jumeau en JavaScript, Introduction (fx-0575), qui fait la même barre avec un écouteur scroll et tourne dans Firefox.

Le code

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

HTML
<div class="sdc-scroll-container">
    <div class="sdc-progress-bar">
      <div class="sdc-progress-fill"></div>
    </div>
    <div class="sdc-progress-content">
      <h3>Barre de progression</h3>
      <p>Scrollez pour voir la barre se remplir. Utilise animation-timeline: scroll() pour lier l'animation au défilement du conteneur.</p>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
    </div>
  </div>
  <span class="sdc-scroll-hint">Scrollez ↓</span>
CSS
.sdc-scroll-container {
  width: 100%;
  height: 280px;
  overflow-y: auto;
  position: relative;
  scroll-behavior: smooth;
}

.sdc-scroll-container::-webkit-scrollbar {
  width: 4px;
}

.sdc-scroll-container::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.05);
}

.sdc-scroll-container::-webkit-scrollbar-thumb {
  background: rgba(139, 92, 246, 0.4);
  border-radius: 2px;
}

.sdc-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  z-index: 5;
  pointer-events: none;
  animation: sdcHintPulse 2s ease-in-out infinite;
}

.sdc-progress-bar {
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.1);
  z-index: 10;
}

.sdc-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #8b5cf6, #ec4899);
  transform-origin: left;
  animation: sdcProgressGrow linear;
  animation-timeline: scroll(nearest);
  transform: scaleX(0);
}

.sdc-progress-content {
  padding: 20px;
  height: 800px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.sdc-progress-content h3 {
  font-size: 1rem;
  font-weight: 700;
  color: #fff;
  margin: 0;
}

.sdc-progress-content p {
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
  line-height: 1.6;
}

.sdc-progress-block {
  height: 120px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

@keyframes sdcHintPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}

@keyframes sdcProgressGrow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}
JavaScript (fx-0555)

Personnaliser

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

Option / propriétéDéfautEffet
Hauteur du cadre (CSS — .sdc-scroll-container et .demo-preview, height: 280px) 280 px / 280 px Les deux sont alignées : le cadre extérieur en overflow: hidden coupe tout ce qui dépasse, dont l'indice. Donnez au bloc défilant la hauteur de votre mise en page (flex: 1 dans une carte, height: 60vh…) : la timeline lit la course réelle, rien d'autre à changer. Sans le cadre .demo-preview, remettez box-sizing: border-box : mesuré sans lui, le padding s'ajoute aux 800 px et la course passe de 524 à 560.
Hauteur du contenu (CSS — .sdc-progress-content, height: 800px) 800 px (course 524 px) C'est un gabarit de démonstration : en production, retirez la hauteur fixe (height: auto) et laissez le texte réel faire la course — 0 % en haut, 100 % au dernier pixel, quelle que soit la longueur. Si le contenu ne déborde pas, la timeline reste inactive et la barre vide (mesuré avec 200 px de contenu).
Piste et remplissage (CSS — .sdc-progress-bar, .sdc-progress-fill) 4 px, blanc à 10 % / dégradé 90° #8b5cf6 → #ec4899 L'épaisseur est celle de la piste, le remplissage suit en height: 100%. Le dégradé est comprimé avec le scaleX : la pointe reste rose à toute position ; pour une couleur unie, une seule valeur. Un border-radius sur le remplissage serait lui aussi écrasé à faible échelle — posez-le sur la piste avec overflow: hidden.
animation-timeline: scroll(nearest) (CSS — .sdc-progress-fill) scroll(nearest) — le plus proche ancêtre qui défile, axe du bloc scroll(root) lit la page entière (mesuré : 0,5 à mi-page, barre en position: fixed) ; scroll(nearest inline) suit un défilement horizontal. Le remplissage doit rester à l'intérieur du cadre : sorti du cadre, nearest remonte au défileur suivant — mesuré, la barre ignore le cadre et suit la page. Pour une barre hors du cadre, nommez la timeline : scroll-timeline-name: --doc sur le cadre, timeline-scope: --doc sur l'ancêtre commun, animation-timeline: --doc sur le remplissage (mesuré : 0,47 à 262 px sur une course de 560).
animation-range (CSS — absent, soit normal) toute la course (0 % → 100 %) animation-range: 0 50% remplit la barre sur la première moitié seulement — mais, faute d'animation-fill-mode: forwards, elle retombe à zéro passé 50 % (mesuré : 0,5 à 131 px, 0 à 262 et 524). Ajoutez animation-fill-mode: forwards (ou both) dès que la plage ne couvre pas toute la course.
Courbe et keyframes (CSS — animation: sdcProgressGrow linear, @keyframes sdcProgressGrow) linear, from scaleX(0) → to scaleX(1) Seule courbe qui rende la proportion exacte. Mesuré avec ease-out : 0,68 à mi-course dans Chromium, 0,47 dans WebKit — deux navigateurs, deux barres. Gardez aussi le from { transform: scaleX(0) } : sans lui, WebKit 26.5 rend le carré de la progression (mesuré 0,25 à mi-course), Chromium non.
Défilement doux (CSS — .sdc-scroll-container, scroll-behavior: smooth) smooth Ne lisse que les défilements programmés et les ancres : mesuré, scrollTop = 262 se relit 0 et arrive en 249 ms dans Chromium, 493 ms dans Firefox ; la molette n'est pas concernée. auto pour un bouton « Aller à l'article 4 » instantané.

FAQ

Quelle différence avec Scroll Progress Bar (fx-0547) ? Les deux barres se ressemblent trait pour trait.

Le cœur est le même : piste de 4 px en position: sticky, remplissage scaleX(0 → 1) sur animation-timeline: scroll(), dégradé #8b5cf6 → #ec4899, contenu de 800 px dans un cadre de 280. fx-0547 écrit scroll() sans argument — nearest est la valeur par défaut, les deux s'accrochent donc au même défileur. Les différences sont dans le cadre : fx-0547 garde un .demo-preview à min-height: 300px (l'indice se pose sous le cadre), fx-0555 le fige à height: 280px; overflow: hidden (l'indice recouvre le bas du cadre) ; fx-0555 écrit le from des keyframes que WebKit exige, fx-0547 non ; et fx-0555 est gratuit là où fx-0547 est premium. Même famille au catalogue : Progress Reveal (fx-0539) anime une variable --sb-progress sur la même timeline pour remplir un mot en background-clip: text, et Progress Reveal (fx-0588) ajoute sous la même piste collée des cartes révélées par view(). Si vous voulez la valeur (un pourcentage en texte, un bouton débloqué à 100 %) ou Firefox, prenez Introduction (fx-0575), le jumeau en JavaScript.

Ma barre reste vide alors que le cadre défile : pourquoi ?

Trois causes, toutes mesurées. Le navigateur n'a pas animation-timeline — Firefox quelle que soit la version, Chrome avant 115, Safari avant 26 : le remplissage reste à scaleX(0), la piste est vide (masquez-la avec @supports not (animation-timeline: scroll())). Le contenu ne déborde pas : un cadre dont le texte fait 200 px pour 280 de haut a une course nulle, la timeline est attachée mais inactive (currentTime nul) et la barre reste à 0 — elle ne se rabat pas sur la page. Le remplissage a été sorti du cadre : scroll(nearest) cherche alors le défileur le plus proche au-dessus de lui, souvent la page — mesuré, cadre défilé de 262 px, barre à 0 ; page défilée à 700 px, barre à 0,47. Dans ce dernier cas, nommez la timeline (scroll-timeline-name sur le cadre, timeline-scope sur l'ancêtre commun).

Peut-on afficher la progression de la page entière plutôt que celle d'un cadre ?

Oui, en deux changements : animation-timeline: scroll(root) sur .sdc-progress-fill — ou scroll() si aucun ancêtre ne défile — et la piste en position: fixed; top: 0 (ou sticky dans votre en-tête), sans le cadre de 280 px. Mesuré sur une page de 3 000 px : 0 en haut, 0,5 à 1 100 px sur 2 200 de course, 1 en bas. La piste doit rester dans le document, pas dans un autre bloc défilant, sinon nearest le préférerait. Sur mobile, la barre d'adresse change clientHeight et donc la course : la barre se recale toute seule, sans écouteur resize — c'est un avantage du CSS natif sur le jumeau JavaScript.