learning-front

Nivel 1 · HTML y CSS: la estructura y la piel

BONUS: La barra de progreso de scroll, en CSS puro

El truco detrás de la barra que se llena arriba en cada capítulo de este curso: animaciones con @keyframes atadas al scroll (animation-timeline), sin una sola línea de JavaScript.

Esto es un extra avanzado. Usa una técnica reciente (scroll-driven animations) y no la necesitas para maquetar; pero es de las cosas que mejor demuestran lo lejos que llega el CSS moderno por sí solo.

Si te has fijado, arriba de cada capítulo de este curso hay una barra fina que se va llenando a medida que lees. No la mueve JavaScript: es CSS puro. Aquí desmontamos el truco. De paso conoces dos cosas nuevas: las animaciones con @keyframes y las animaciones ligadas al scroll.

De transition a @keyframes#

En el bonus de acabado visual usaste transition: suaviza el cambio entre dos estados cuando algo cambia (un :hover). Una animación va más allá: defines una secuencia con @keyframes y se reproduce sola.

css
/* define una secuencia llamada "crecer" */
@keyframes crecer {
  /* estado inicial de la animación */
  from {
    /* arranca encogido a nada en horizontal */
    transform: scaleX(0);
  }
  /* estado final de la animación */
  to {
    /* termina a su tamaño completo */
    transform: scaleX(1);
  }
}

@keyframes crecer describe el viaje: empieza en scaleX(0) y termina en scaleX(1) (qué hace exactamente scaleX lo vemos en el apartado siguiente). Por sí solo no hace nada; hay que reproducirlo con la propiedad animation en algún elemento. (Puedes usar porcentajes —0%, 50%, 100%— en vez de from/to para tener más etapas.)

transform: scaleX: una barra que crece#

scaleX escala un elemento solo en horizontal: scaleX(1) es su tamaño normal, scaleX(0) lo encoge a nada, scaleX(0.5) lo deja a la mitad. Si animamos una barra de scaleX(0) a scaleX(1), crece de vacía a llena.

El detalle que más despista: desde dónde crece. Por defecto, transform toma como origen el centro del elemento, así que scaleX escala hacia los dos lados. Para que una barra de progreso crezca desde la izquierda, hay que mover ese origen:

css
.progreso {
  /* borde izquierdo */
  transform-origin: 0 50%;
}

Atar la animación al scroll: animation-timeline#

Aquí está la magia. Normalmente una animación avanza con el tiempo: su duration. Pero su “reloj” se puede cambiar. Con animation-timeline: scroll(), el reloj pasa a ser la barra de scroll: la animación está al 0% cuando estás arriba del todo y al 100% cuando llegas abajo.

css
.progreso {
  /* reproduce los @keyframes "crecer"; duración auto (la marca el scroll) */
  animation: crecer auto linear;
  /* el "reloj" es el scroll vertical de la página, no el tiempo */
  animation-timeline: scroll(root block);
}

Como el avance lo marca el scroll y no el tiempo, la duración se pone en auto. scroll(root block) dice: “usa el scroll vertical (block) de la página entera (root)”. Y ya está: la barra se llena exactamente al ritmo al que lees.

La barra, entera#

Juntando las tres piezas —barra fija, animación de scaleX, reloj de scroll—:

css
.progreso {
  /* saca la barra del flujo y la ancla a la ventana */
  position: fixed;
  /* pegada al borde superior */
  top: 0;
  /* desde el borde izquierdo... */
  left: 0;
  /* ...hasta el derecho: ocupa todo el ancho */
  right: 0;
  /* grosor de la barra */
  height: 3px;
  /* degradado horizontal de color (el mismo del playground) */
  background: linear-gradient(90deg, #b8336a, #6a5af0);
  /* estado base: invisible si no hay soporte */
  transform: scaleX(0);
  /* crece desde la izquierda */
  transform-origin: 0 50%;
  /* nunca roba un clic */
  pointer-events: none;
  /* reproduce los @keyframes "crecer" */
  animation: crecer auto linear;
  /* atado al scroll vertical de la página */
  animation-timeline: scroll(root block);
}
@keyframes crecer {
  /* scroll arriba del todo: barra vacía */
  from { transform: scaleX(0); }
  /* scroll abajo del todo: barra llena */
  to { transform: scaleX(1); }
}

Dos detalles de oficio que separan lo que funciona de lo que está bien hecho:

  • El estado base transform: scaleX(0) es la red de seguridad. Las scroll-driven animations son recientes (Chrome/Edge 115+, Firefox 136+, Safari 26+). En un navegador antiguo, animation-timeline se ignora y la barra se queda con su estilo base. Si ese estilo es scaleX(0), queda invisible —nadie ve algo roto—; si no lo declaras, queda llena y fija.
  • pointer-events: none y aria-hidden (este último, en el HTML): la barra es decoración. No debe interceptar clics ni anunciarse a un lector de pantalla.

Esto es, casi tal cual, el CSS que pinta la barra de progreso de este mismo curso.

Pruébalo#

Haz scroll dentro del preview y mira la barra llenarse. Luego, en styles.css, quita la línea transform-origin: 0 50% y vuelve a desplazarte: verás cómo crece desde el centro, el fallo más típico.

Comprueba lo que sabes#

Pregunta 1 de 4

¿En qué se diferencia una animación con `@keyframes` de una `transition`?

Tu turno#

La página de lectura no da pista de cuánto queda. Conviértela en una con barra de progreso: completa el TODO de starter/styles.css. Cuando la tengas, despliega las soluciones y fíjate en el detalle del transform-origin y del estado base.

Ejercicio · en esta página

Construye la barra de progreso de scroll

La página de lectura es larga y no da pista de cuánto queda. Convierte el <div class="progreso"> en una barra que se llene arriba al hacer scroll, con CSS puro: una animación de scaleX atada al scroll con animation-timeline. Necesitas un navegador con scroll-driven animations (Chrome/Edge 115+, Firefox 136+, Safari 26+).

Paso 1: Que reaccione al scroll

  • La barra está fija arriba y cambia a medida que haces scroll.
  • Usa `@keyframes` de `scaleX(0)` a `scaleX(1)` con `animation-timeline: scroll()`.
Ver soluciones
/* SOLUCIÓN OK — la barra reacciona al scroll, pero con dos fallos.
   Crece desde el centro (falta transform-origin) y no tiene estado base, así que
   en navegadores que no soportan scroll() se queda llena todo el rato. */

body {
  margin: 0;
  font-family:
    system-ui,
    -apple-system,
    "Segoe UI",
    Roboto,
    sans-serif;
  background: #f7f6fb;
  color: #1c1b22;
  line-height: 1.6;
}

.progreso {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: #b8336a;
  /* Sin transform-origin, scaleX crece desde el CENTRO hacia los lados: raro.
     Y sin un transform base, donde no se soporta scroll() la barra se queda en
     scaleX(1), es decir, llena y fija. */
  animation: progreso-crecer auto linear;
  animation-timeline: scroll(root block);
}

@keyframes progreso-crecer {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

.cab {
  max-width: 38rem;
  margin: 0 auto;
  padding: 3rem 1.25rem 1rem;
}
.kicker {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #b8336a;
  margin: 0 0 0.25rem;
}
.cab h1 {
  font-size: 1.8rem;
  margin: 0;
}

.articulo {
  max-width: 38rem;
  margin: 0 auto;
  padding: 0 1.25rem 2rem;
}
.articulo h2 {
  font-size: 1.2rem;
  margin: 2rem 0 0.5rem;
}
.articulo p {
  margin: 0 0 1rem;
  color: #2b2a33;
}

.pie {
  max-width: 38rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 3rem;
  border-top: 1px solid #e6e3ee;
  color: #5b5966;
  font-size: 0.85rem;
}

Por qué este nivel

  • El mecanismo está bien: `@keyframes` de `scaleX(0)` a `scaleX(1)` reproducido con `animation-timeline: scroll(root block)`. La barra reacciona al scroll.
  • Pero crece desde el centro hacia los lados: falta `transform-origin: 0 50%` para anclarla al borde izquierdo.
  • Y sin un `transform` base, en un navegador sin soporte de `scroll()` la barra se queda en `scaleX(1)`: llena y fija, justo lo contrario de lo que quieres.