Cómo crear un indicador de progreso de desplazamiento

Un indicador de progreso de desplazamiento es un patrón de interfaz de usuario común que comunica visualmente el progreso del usuario a través de un documento o contenedor desplazable. A medida que el usuario se desplaza, un elemento visual se actualiza para reflejar su posición, lo que proporciona una idea clara e intuitiva de cuánto contenido se vio y cuánto queda.

Cómo implementarlo

Para crear un indicador de progreso de desplazamiento, necesitas lo siguiente:

  1. Un elemento que actúe como barra de progreso Por lo general, este elemento es position: fixed o position: absolute para que permanezca visible mientras el usuario se desplaza.
  2. Es una animación vinculada a la posición de desplazamiento.

Estas son algunas sugerencias para lograrlo:

  • Primero, crea un elemento HTML que sirva como barra de progreso. Puedes aplicar el estilo que quieras a este elemento.
  • A continuación, en tu CSS, define una animación @keyframes que escale la barra de progreso. Un enfoque común es escalar el elemento de scaleX(0) a scaleX(1).
  • Por último, aplica esta animación al elemento de la barra de progreso y establece su animation-timeline en un cronograma de desplazamiento. Esto le indica al navegador que impulse el progreso de la animación en función de la posición de desplazamiento del elemento de desplazamiento superior más cercano.

Ejemplo de código

Este código aumenta el tamaño del elemento #progress cuando se desplaza con un cronograma de desplazamiento anónimo, creado por la función scroll().

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: scroll();
    }
  }
}

Debido a su ubicación en el DOM, la función scroll() hará un seguimiento de su elemento de desplazamiento superior más cercano en la dirección block, que aquí es el elemento de desplazamiento raíz.

<body>
  <!-- MANDATORY: Purely decorative visual scroll progress bars MUST set aria-hidden="true" to remove the empty element from the assistive technology reading tree -->
  <div id="progress" aria-hidden="true"></div>
</body>

Este código aumenta el tamaño del elemento #progress cuando se desplaza con un cronograma de desplazamiento con nombre, creado por la propiedad scroll-timeline.

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    :root {
      scroll-timeline: --tl block;
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: --tl;
    }
  }
}

Prácticas recomendadas

Cuando uses animaciones controladas por el desplazamiento, es importante que sigas algunas prácticas recomendadas para garantizar una experiencia fluida y accesible:

  • SÍ incluye la detección de funciones: No todos los navegadores admiten animaciones controladas por el desplazamiento. Usa @supports (animation-timeline: scroll()) para verificar la compatibilidad y proporcionar un resguardo para los navegadores que no lo admiten.
    • NO uses el paquete scroll-timeline-polyfill para la estrategia de resguardo, ya que no tiene todas las funciones y tiene muchos problemas conocidos.
    • Si la animación solo se considera decorativa, opta por la mejora progresiva y NO proporciones un resguardo.
  • SÍ quites los elementos puramente decorativos de los flujos de lectura de las tecnologías de accesibilidad: Aplica aria-hidden="true" a los indicadores de desplazamiento puramente visuales para asegurarte de que los lectores de pantalla no encuentren nodos vacíos sin nombre.
  • SÍ respeta las preferencias del usuario: Algunos usuarios prefieren tener menos movimiento en la Web. Usa la consulta de medios prefers-reduced-motion para inhabilitar o reducir las animaciones para estos usuarios.
  • SÍ intenta animar solo las propiedades de CSS que tengan un buen rendimiento: Para obtener las animaciones más fluidas, limítate a animar las propiedades que pueda controlar el subproceso del compositor del navegador, como transform y opacity. Animar otras propiedades, como width o height, puede provocar problemas de rendimiento.
  • SÍ uses el orden de declaración correcto: Cuando uses la propiedad abreviada animation, declara animation-timeline después de ella para evitar que la abreviatura restablezca la línea de tiempo.

Cuando uses la función scroll() para crear una animación controlada por el desplazamiento, ten en cuenta lo siguiente:

  • OPCIONAL Sé explícito sobre el elemento de desplazamiento: Cuando no se oriente al elemento de desplazamiento principal más cercano, sé explícito sobre qué elemento de desplazamiento deseas usar con scroll(root) o scroll(self).
    • Cuando root, nearest o self no sean suficientes, usa un scroll-timeline con nombre.
  • OPCIONAL Sé explícito sobre el eje que se debe seguir: Cuando no se oriente al eje block predeterminado (como en un desplazador horizontal), sé explícito sobre qué eje se debe seguir con scroll(block) o scroll(inline).

Cuando uses la propiedad scroll-timeline para crear una animación controlada por el desplazamiento, ten en cuenta lo siguiente:

  • SÍ uses un <dashed-ident> de CSS para el nombre.
  • OPCIONAL Sé explícito sobre el eje que se debe seguir: Cuando no se oriente al eje block predeterminado (como en un desplazador horizontal), sé explícito sobre qué eje se debe seguir con scroll-timeline-axis.
  • SÍ asegúrate de que el alcance de la búsqueda funcione: Cuando el elemento que declara el scroll-timeline no es un ancestro de árbol plano del elemento animado, eleva la visibilidad del nombre del scroll-timeline con timeline-scope en un ancestro compartido.

Estrategias de resguardo

En el caso de los navegadores que no admiten animaciones basadas en el desplazamiento, puedes usar un resguardo para recrear los efectos visuales. Por lo general, las alternativas se crean con un objeto de escucha de desplazamiento (para los efectos de ScrollTimeline) o la API de IntersectionObserver (para los efectos de ViewTimeline).

En los navegadores con compatibilidad integrada para animaciones basadas en el desplazamiento, usa SIEMPRE la implementación nativa de CSS, ya que tiene un mejor rendimiento.

Ten en cuenta que no todos los efectos se pueden recrear con el enfoque de resguardos.

Específicamente para este caso de uso, la siguiente secuencia de comandos aplica el resguardo para los navegadores que no admiten animaciones controladas por el desplazamiento. Usa un objeto de escucha de desplazamiento para hacer un seguimiento de la posición de desplazamiento del elemento raíz y actualiza la propiedad transform de la barra de progreso según corresponda.

<script>
  if (!CSS.supports('animation-timeline', 'scroll()')) {
    const progress = document.querySelector('#progress');

    window.addEventListener('scroll', () => {
      const scrollable = document.documentElement.scrollHeight - window.innerHeight;
      const scrolled = window.scrollY;
      const progressPercentage = (scrolled / scrollable);

      progress.style.transform = `scaleX(${progressPercentage})`;
    });
  }
</script>