Animación progress bar
Como crear una animación de progress bar sencilla usando solo CSS, para ir recorriendo el progreso de lectura en nuestras webs.
Algo en lo que me fije después de escribir unos cuantos articulos es que la tasa de lectura no es muy alta. A más grande el articulo menor es su tasa de lectura. Por eso recordé esos "Tiktok" que salen a pantalla dividida donde sale arriba el video de unos 30 segundos y debajo un video de una carrera de coches.
Parece que la retención del publico es complicada, tenemos una atención limitada y por eso se me ocurrió implementar en el blog una animación sencilla con css para que puedas ver el progreso que llevas en la página. ¿Servirá para aumentar la retención? No lo se, pero ya que lo hice, te muestro lo sencillo que es implementarlo.
Puedes conseguirlo solo con CSS, usando las scroll-driven animations.
La idea en 30 segundos
Antes, el flujo era:
Escuchar
scrollcon JavaScript.Calcular
(scrollY / alturaTotal) * 100.Aplicar ese valor al
widthotransformde la barra.
Con scroll progress timelines, el navegador hace ese mapeo por ti:
0% del scroll → inicio de la animación (
scaleX(0)).100% del scroll → final de la animación (
scaleX(1)).
La animación no corre en el tiempo: corre en función de cuánto has scrolleado.
Paso 1: El HTML mínimo
Solo necesitas un contenedor y la barra que se va a “rellenar”:
<div class="reading-progress-bar" aria-hidden="true">
<div class="reading-progress-bar__fill"></div>
</div>
El contenedor va fijo arriba (
position: fixed).La barra interior es la que animamos.
aria-hidden="true"porque es decorativa: la barra del navegador ya indica el scroll.
En Angular, React, Vue, etc esto es un componente con ese código. En este blog, por ejemplo, lo montamos solo en la página de detalle del artículo, no en toda la web.
Paso 2: Fijar la barra arriba
.reading-progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 4px;
z-index: 60; /* por encima del header si lo tienes sticky/fixed */
pointer-events: none; /* no bloquea clics en enlaces del header */
}
pointer-events: none es fácil de olvidar y molesto si no lo pones: una capa fija en top: 0 puede interceptar clics en el menú.
Paso 3: La barra interior y por qué scaleX y no width
.reading-progress-bar__fill {
display: block;
width: 100%;
height: 100%;
background: #e9c400;
transform: scaleX(0);
transform-origin: left center;
}
¿Por qué transform: scaleX() en lugar de animar width?
transformsuele ir en el hilo de composición del navegador → animación más fluida.widthpuede provocar más recálculos de layout.Con
scaleX(0)→scaleX(1)la barra “crece” desde la izquierda si ponestransform-origin: left center.
Estado inicial: scaleX(0) (invisible). Estado final de la animación: scaleX(1) (barra completa).
Paso 4: La animación ligada al scroll
Aquí está la magia. Primero defines los keyframes:
@keyframes reading-progress-bar-grow {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
Luego enlazas esa animación al progreso del scroll del documento:
@supports (animation-timeline: scroll()) {
.reading-progress-bar__fill {
animation: reading-progress-bar-grow linear both;
animation-timeline: scroll(root block);
}
}
Propiedad | Qué hace |
|---|---|
| Animación lineal; |
| La “línea de tiempo” es el scroll del documento ( |
Paso 5: Degradación con @supports
No todos los navegadores soportan scroll timelines todavía. Envuelve la animación en:
@supports (animation-timeline: scroll()) {
/* ... animación ... */
}
Si el navegador no lo entiende, la barra se queda en scaleX(0) y no se ve. La página sigue funcionando igual. Es mejor que mostrar una barra rota o a medias.
Soporte aproximado (2026): caniuse.com
Paso 6: Accesibilidad — prefers-reduced-motion
Quien tiene reducción de movimiento activada no necesita una barra animada extra (ya tiene la del scrollbar):
@media (prefers-reduced-motion: reduce) {
.reading-progress-bar {
display: none;
}
}