UtilitairesGratuit

Scroll Progress Bar

Fine barre en dégradé qui se remplit pendant la lecture : elle suit le défilement de la page, ou celui d'un bloc que vous désignez.

JSScrollProgress

Mis à jour le

Faites défiler ce bloc : la barre suit votre lecture.

Chaque paragraphe que vous lisez rapproche la barre de la fin du bloc.

La barre ne compte que ce bloc, pas la page entière : c'est le réglage data-rpb-target.

Sans ce réglage, elle suit la page et se fixe en haut de la fenêtre.

Le lecteur d'écran annonce la progression, de 0 à 100 %.

Vous êtes presque au bout : encore un paragraphe.

Fin du bloc. La barre est pleine.

Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Utilitaires en contient 43 au total, dont 3 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Les Heures de Valcorbe Chapitre 4 — La crue

IV. La crue

La rivière avait monté pendant la nuit. Au matin, les quais bas de Valcorbe dormaient sous une nappe grise qui charriait des caisses, des branches et, de temps à autre, une chaise.

Inès la regardait depuis le pont, les mains au fond de son manteau. Son père répétait que la ville se souvenait de chaque crue : les pierres en gardaient la trace, un trait au-dessus de l'autre.

Elle compta sept traits sur le pilier du vieux moulin. Le plus haut portait une date qu'elle ne savait plus lire, polie par un siècle de mains.

Derrière elle, la cloche de la mairie sonna neuf coups. Personne n'ouvrit les boutiques. Le boulanger avait posé ses pains sur le rebord du premier étage, pour qui pourrait les atteindre.

Une barque déboucha à l'angle de la place, menée par deux garçons qui s'amusaient visiblement bien plus qu'ils n'auraient dû. Inès leva la main ; ils répondirent d'un coup de rame.

À midi, l'eau cessa de monter. Il lui faudrait trois jours pour redescendre, et trois semaines pour que l'odeur de vase quitte les caves. Mais la ville, comme toujours, ajouterait un trait à la pierre.

Fin du chapitre 4

Éditions Ombrevieille Chapitre 4 sur 12

① Liseuse en ligne — où l'on en est dans un chapitre

QuandLe lecteur fait défiler le chapitre 4 des Heures de Valcorbe dans la colonne de lecture. La barre, juste sous l'en-tête, s'allonge et montre d'un coup d'œil ce qu'il reste avant la fin du chapitre.
PourquoiDans une liseuse, l'en-tête et le pied restent en place et seul le texte défile : la barre suit ce bloc, pas la page. Avec 4 px et aucun clic intercepté, elle ne gêne jamais la lecture.
Réglagesdata-rpb-target="#lq35-livre", racine en premier enfant de la colonne de lecture, aria-labelledby vers le titre du chapitre. Au chargement, la scène fait défiler le chapitre à 32 % pour que la barre se voie tout de suite.

② Fenêtre de conditions — lire jusqu'au bout avant d'accepter

QuandUne fenêtre présente les conditions d'un service de réservation d'ateliers. Le bouton « J'accepte » reste grisé tant que la barre n'a pas atteint le bas du texte, puis il s'active.
PourquoiLa valeur de la barre se lit dans le code : aria-valuenow passe à 100 quand le bas est atteint. La scène s'en sert pour activer le bouton, sans refaire le calcul du défilement, et la personne voit ce qu'il lui reste à lire.
Réglagesdata-rpb-target="#lq35-cgu", --rpb-height: 3px, rail gris --rpb-track: #eef0f4. Un MutationObserver de la scène surveille aria-valuenow et active le bouton à partir de 99. Pré-défilement à 45 %.

③ Choix de photos — repère dans une pellicule étroite

QuandDans la fenêtre « Choisir des photos », la colonne de vignettes défile à gauche du grand aperçu. La barre en haut de la colonne indique où l'on se trouve dans la pellicule.
PourquoiUne colonne de 96 px n'a pas la place pour une barre de défilement confortable (masquée ici). La barre de progression la remplace en haut, et son dégradé ressort sur le fond sombre de la fenêtre.
Réglagesdata-rpb-target="#lq35-pellicule", --rpb-height: 4px, rail --rpb-track: #2b2f3a, marges latérales négatives pour couvrir toute la largeur de la colonne. Pré-défilement à 40 %.

Comment ça marche

Collez la racine .fx-0735-root n'importe où dans la page. Sans réglage, elle se fixe en haut de la fenêtre (position: fixed, 4 px de haut, z-index: 1000) et sa barre intérieure .rpb-bar s'allonge de 0 à 100 % à mesure que la page défile. La longueur passe par transform: scaleX(), avec un amorti de 0,1 s.

Pour suivre un bloc qui défile seul (article dans une fenêtre, fil de messages, colonne de vignettes), ajoutez data-rpb-target avec le sélecteur CSS de ce bloc et placez la racine en premier enfant du bloc. Elle passe alors en position: sticky et reste collée en haut du bloc, sans pousser le texte (marge basse négative de la même hauteur). Si le sélecteur ne trouve rien, cette barre-là ne s'active pas.

Le calcul est refait à chaque défilement (au plus une fois par image affichée), au redimensionnement de la fenêtre et quand la hauteur du contenu change (ResizeObserver : images chargées, texte qui s'allonge). Dans un bloc désigné, seuls les éléments présents au chargement sont surveillés : après en avoir ajouté (nouveaux messages), appelez root.fx0735.update() ou attendez le prochain défilement. Quand rien ne dépasse, il n'y a rien de plus à lire et la barre reste vide. Chaque racine a sa propre instance : plusieurs barres dans la même page, ou le code collé deux fois, ne se gênent pas.

Accessibilité

  • Vraie barre de progression : la racine porte role="progressbar", aria-valuemin="0", aria-valuemax="100" et un aria-valuenow entier tenu à jour. Son nom vient de aria-label (« Progression de lecture ») ; remplacez-le, ou pointez aria-labelledby vers le titre de l'article.
  • Silencieuse : la valeur n'est pas une zone live. Un lecteur d'écran la lit quand on arrive dessus et ne parle pas à chaque défilement.
  • Mouvement réduit : sous prefers-reduced-motion: reduce, l'amorti de 0,1 s disparaît et la barre suit le défilement sans délai. Elle n'anime rien d'elle-même : c'est l'utilisateur qui fait défiler.
  • Jamais sur le chemin d'un clic : pointer-events: none sur la racine, la barre laisse passer les clics vers le menu situé dessous.
  • Repère complémentaire : 4 px de couleur restent un indice visuel. Quand la lecture complète compte vraiment, affichez aussi un texte (« 3 min restantes », « 40 % lu »).

Compatibilité navigateur

JavaScript sans dépendance, transform et position: sticky (mode bloc, Chrome 56, Firefox 32, Safari 13). ResizeObserver : Chrome 64, Firefox 69, Safari 13.1. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 (Playwright) : une barre sur la page et une sur un bloc désigné dans la même page, code collé deux fois, molette et toucher, 390 px, mouvement réduit, aucun nom ajouté sur window, 0 erreur.

Chrome 64+✓ Complet (mesuré)
Firefox 69+✓ Complet (mesuré)
Safari 13.1+✓ Complet (mesuré dans WebKit)
Edge 79+✓ Complet (moteur Chromium)
Mobile iOS 13.4+✓ Moteur WebKit, non vérifié sur appareil
Android Chrome 64+✓ Complet (toucher mesuré)

Sans JavaScript, la barre reste vide et la page se lit normalement. Sans ResizeObserver, elle se recalcule au défilement et au redimensionnement de la fenêtre, mais pas quand le contenu grandit tout seul.

Le code

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

HTML
<div class="fx-0735-root" role="progressbar" aria-label="Progression de lecture" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
  <div class="rpb-bar"></div>
</div>
CSS
.fx-0735-root,
.fx-0735-root *,
.fx-0735-root *::before,
.fx-0735-root *::after {
  box-sizing: border-box
}

/* Par défaut : barre fixée en haut de la fenêtre, elle suit le défilement de la page. */
.fx-0735-root {
  --rpb-height: 4px;
  --rpb-track: transparent;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  height: var(--rpb-height);
  background: var(--rpb-track);
  pointer-events: none;
}

/* Avec data-rpb-target : placez la racine en premier enfant du conteneur qui défile,
   elle reste collée en haut de ce conteneur sans pousser son contenu. */
.fx-0735-root[data-rpb-target] {
  position: sticky;
  margin-bottom: calc(-1 * var(--rpb-height));
}

.rpb-bar {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, #6366f1, #8b5cf6, #d946ef);
  transition: transform 0.1s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .rpb-bar {
    transition: none
  }
}
JavaScript (fx-0735)
(function () {
  // Une instance par racine. Sans data-rpb-target : la page. Avec : le conteneur désigné (sélecteur CSS).
  function run() {
  document.querySelectorAll('.fx-0735-root').forEach(function (root) {
    if (root.fx0735) return;
    var bar = root.querySelector('.rpb-bar');
    var sel = root.getAttribute('data-rpb-target');
    var box = sel ? document.querySelector(sel) : null;
    if (!bar || (sel && !box)) return;
    var queued = false;

    function update() {
      queued = false;
      var el = box || document.scrollingElement || document.documentElement;
      var max = el.scrollHeight - el.clientHeight;
      var p = max > 0 ? Math.min(1, Math.max(0, el.scrollTop / max)) : 0;
      bar.style.transform = 'scaleX(' + p + ')';
      root.setAttribute('aria-valuenow', Math.round(p * 100));
    }
    function schedule() {
      if (!queued) { queued = true; requestAnimationFrame(update); }
    }

    (box || window).addEventListener('scroll', schedule, { passive: true });
    window.addEventListener('resize', schedule);
    // Le contenu change de hauteur (images, texte chargé) : on recalcule.
    if ('ResizeObserver' in window) {
      var ro = new ResizeObserver(schedule);
      ro.observe(box || document.body);
      if (box) Array.prototype.forEach.call(box.children, function (c) { ro.observe(c); });
    }
    root.fx0735 = { update: update };
    update();
  });
  }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run); else run();
})();

Personnaliser

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

Option / propriétéDéfautEffet
Bloc suivi (HTML — data-rpb-target) absent : la page Sélecteur CSS du bloc qui défile (#article, .fil-messages). Placez alors la racine en premier enfant de ce bloc.
Épaisseur (CSS — --rpb-height) 4px 2 à 3 px pour un trait discret, 6 px pour une barre bien visible sur mobile.
Rail (CSS — --rpb-track) transparent Une couleur (#eef0f4) dessine la longueur totale sous la barre, utile quand la lecture complète compte.
Couleurs (CSS — background de .rpb-bar) dégradé #6366f1 → #8b5cf6 → #d946ef Une couleur unie ou le dégradé de votre charte. Le dégradé s'étire avec la barre.
Position sous un en-tête fixe (CSS — top de .fx-0735-root) 0 top: 64px place la barre juste sous un en-tête de 64 px, au lieu de le recouvrir.
Nom lu (HTML — aria-label) « Progression de lecture » Remplacez par le titre utile, ou utilisez aria-labelledby vers un titre visible.
Recalcul immédiat (JS — root.fx0735.update()) — Après avoir ajouté ou remplacé des éléments dans le bloc suivi, pour remettre la barre à jour sans attendre un défilement.

FAQ

La barre peut-elle mesurer seulement l'article, pas le pied de page ?

En mode page, elle mesure tout le document, pied de page compris : elle atteint 100 % tout en bas. Pour ne mesurer que l'article, il faut que l'article défile dans son propre bloc (hauteur fixe et overflow-y: auto) et le désigner avec data-rpb-target. Le code ne sait pas découper la page en sections.

Pourquoi ma barre reste vide ?

Trois causes possibles : la page ne dépasse pas la fenêtre (rien à faire défiler) ; le sélecteur de data-rpb-target ne trouve aucun élément ; ou votre site fait défiler un conteneur (un div avec overflow: auto qui remplit l'écran) au lieu de la page. Dans ce dernier cas, donnez son sélecteur à data-rpb-target.

Peut-on mettre plusieurs barres sur la même page ?

Oui. Chaque racine .fx-0735-root a sa propre instance : une barre pour la page et une par bloc désigné. Le script peut même être collé deux fois : une racine déjà prise en charge est ignorée.