Criar um indicador de progresso de rolagem

Um indicador de progresso de rolagem é um padrão comum de interface do usuário que comunica visualmente o progresso do usuário em um documento ou contêiner rolável. Conforme o usuário rola a tela, um elemento visual é atualizado para refletir a posição dele, fornecendo uma noção clara e intuitiva de quanto conteúdo foi visualizado e quanto ainda resta.

Como fazer a implementação

Para criar um indicador de progresso de rolagem, você precisa de duas coisas:

  1. Um elemento para atuar como a barra de progresso. Esse elemento normalmente é position: fixed ou position: absolute para que permaneça visível enquanto o usuário rola a página.
  2. Uma animação vinculada à posição de rolagem.

Confira como fazer isso:

  • Primeiro, crie um elemento HTML que vai servir como barra de progresso. Esse elemento pode ser estilizado como você quiser.
  • Em seguida, no CSS, defina uma animação @keyframes que dimensione a barra de progresso. Uma abordagem comum é dimensionar o elemento de scaleX(0) para scaleX(1).
  • Por fim, aplique essa animação ao elemento da barra de progresso e defina o animation-timeline como uma linha do tempo de rolagem. Isso informa ao navegador para direcionar o progresso da animação com base na posição de rolagem do ancestral mais próximo.

Exemplo de código

Esse código aumenta o elemento #progress na rolagem usando uma linha do tempo de rolagem anônima, criada pela função 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();
    }
  }
}

Devido à localização no DOM, a função scroll() vai rastrear o scroller ancestral mais próximo na direção block, que aqui é o scroller raiz.

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

Esse código aumenta o elemento #progress na rolagem usando uma linha do tempo de rolagem nomeada, criada pela propriedade 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áticas recomendadas

Ao usar animações acionadas por rolagem, é importante seguir algumas práticas recomendadas para garantir uma experiência tranquila e acessível:

  • FAÇA a detecção de recursos: nem todos os navegadores oferecem suporte a animações acionadas por rolagem. Use @supports (animation-timeline: scroll()) para verificar a compatibilidade e fornecer um substituto para navegadores que não oferecem suporte.
    • NÃO use o pacote scroll-timeline-polyfill para a estratégia de substituição, porque ele não tem todos os recursos e tem muitos problemas conhecidos.
    • Se a animação for considerada apenas decorativa, opte pelo aprimoramento progressivo e NÃO forneça um substituto.
  • REMOVA elementos puramente decorativos dos fluxos de leitura de tecnologia adaptativa: aplique aria-hidden="true" a indicadores de rolagem puramente visuais para garantir que os leitores de tela não encontrem nós vazios e sem nome.
  • RESPEITE as preferências do usuário: algumas pessoas preferem menos movimento na Web. Use a consulta de mídia prefers-reduced-motion para desativar ou reduzir as animações para esses usuários.
  • FAÇA o possível para animar apenas propriedades CSS de alto desempenho: para animações mais suaves, anime propriedades que podem ser processadas pela linha de execução do compositor do navegador, como transform e opacity. Animar outras propriedades, como width ou height, pode causar problemas de desempenho.
  • RECOMENDADO: use a ordem de declaração correta. Ao usar a propriedade abreviada animation, declare animation-timeline depois dela para evitar que a abreviação redefina a linha do tempo.

Ao usar a função scroll() para criar uma animação acionada por rolagem:

  • OPCIONAL Seja explícito sobre o botão de rolagem: quando não estiver segmentando o botão de rolagem ancestral mais próximo, seja explícito sobre qual botão você quer usar com scroll(root) ou scroll(self).
    • Quando root, nearest ou self não forem suficientes, use uma linha do tempo de rolagem nomeada.
  • OPCIONAL Seja explícito sobre o eixo a ser rastreado: quando não estiver segmentando o eixo block padrão (como em um rolador horizontal), seja explícito sobre qual eixo rastrear com scroll(block) ou scroll(inline).

Ao usar a propriedade scroll-timeline para criar uma animação acionada por rolagem:

  • USE um CSS <dashed-ident> para o nome.
  • OPCIONAL: seja explícito sobre o eixo a ser rastreado. Quando não estiver segmentando o eixo block padrão (como em um scroller horizontal), seja explícito sobre qual eixo rastrear com scroll-timeline-axis.
  • VERIFIQUE se o escopo da pesquisa funciona: quando o elemento que declara o scroll-timeline não é um ancestral de árvore simples do elemento animado, eleve a visibilidade do nome do scroll-timeline usando timeline-scope em um ancestral compartilhado.

Estratégias de fallback

Para navegadores que não oferecem suporte a animações acionadas por rolagem, use um substituto para recriar os efeitos visuais. Os substitutos geralmente são criados com um listener de rolagem (para efeitos de ScrollTimeline) ou com a API IntersectionObserver (para efeitos de ViewTimeline).

Em navegadores com suporte integrado para animações de rolagem, use SEMPRE a implementação nativa de CSS, porque ela tem melhor desempenho.

Nem todos os efeitos podem ser recriados usando a abordagem de substitutos.

Para esse caso de uso específico, o script a seguir aplica o substituto para navegadores que não oferecem suporte a animações de rolagem. Ele usa um listener de rolagem para rastrear a posição de rolagem do elemento raiz e atualiza a propriedade transform da barra de progresso de acordo.

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