Cabeçalho que diminui ao rolar

Um cabeçalho que diminui ao rolar é um padrão de interface comum em que um elemento de cabeçalho fixo na parte de cima da página faz uma transição suave para um tamanho menor conforme o usuário rola para baixo. Esse efeito é frequentemente usado para maximizar o espaço da tela para o conteúdo principal, mantendo os elementos essenciais de navegação ou elemento de marca acessíveis. Com as animações de rolagem do CSS, esse efeito pode ser alcançado de forma declarativa e eficiente, vinculando uma animação à posição de rolagem do documento.

Como fazer a implementação

Veja como criar um cabeçalho que diminui ao rolar a tela:

  1. Crie um cabeçalho fixo: comece com um elemento de cabeçalho fixo na parte de cima da página e com uma altura predefinida.

    <header>HEADER</header>
    
    header {
      position: fixed;
      height: 200px;
      top: 0;
      left: 0;
      right: 0;
    }
    
  2. Defina a animação de redução: crie uma animação CSS que mude a altura do cabeçalho.

    @keyframes shrink {
      to {
        height: 50px;
      }
    }
    
  3. Aplicar a animação e a linha do tempo de rolagem: anexe a animação ao cabeçalho e use a função scroll() para vinculá-la à posição de rolagem do documento.

    header {
      animation: shrink auto linear both;
      animation-timeline: scroll(block root);
    }
    
  4. Defina o animation-range: use a propriedade animation-range para especificar a distância de rolagem em que a animação deve ocorrer. Por exemplo, para reduzir o cabeçalho nos primeiros 150 pixels de rolagem, use animation-range: 0px 150px;.

    header {
      animation-range: 0px 150px;
    }
    

Dica: para evitar que o conteúdo após o cabeçalho seja ocultado por ele, adicione um padding-top ao body (ou ao contêiner de conteúdo principal) que seja igual à altura inicial do cabeçalho.

Dica: para garantir que o conteúdo da página role em sincronia com o cabeçalho que está diminuindo, defina o animation-range-end como a diferença entre os tamanhos inicial e final. Isso garante que a animação seja concluída exatamente quando o cabeçalho atingir o tamanho final. Nesta demonstração, o cabeçalho diminui de 200px para 50px, então o animation-range-end é definido como 150px.

Exemplo de código

@keyframes shrink {
  to {
    height: 50px;
  }
}

header {
  animation: shrink auto linear both;
  animation-timeline: scroll(block root);
  animation-range: 0px 150px;
}

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()) and (animation-range: 0% 100%)) para verificar a compatibilidade e fornecer um substituto para navegadores que não oferecem suporte.
    • A verificação (animation-range: 0% 100%) PRECISA ser incluída aqui para filtrar navegadores com suporte parcial.
    • 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.
  • 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.
  • USE a ordem de declaração correta: ao usar a propriedade abreviada animation, declare animation-timeline e animation-range 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).

Quanto a este caso de uso específico:

  • O elemento que você anima NÃO pode ser position: static ou position: relative ao usar porcentagens no animation-range.
    • Isso ocorre porque esses elementos são considerados "em fluxo". Reduzir esses elementos ao rolar a tela diminuiria a distância total de rolagem, afetando o valor calculado de, por exemplo, 10% na rolagem.

Suporte ao navegador e estratégias de substituição

Portanto, uma estratégia de fallback geralmente é necessária.

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 documento em uma distância de 150px e atualiza a altura do cabeçalho de acordo.

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: scroll()) and (animation-range: 0% 100%)')) {
  const header = document.querySelector('header');

  const initialHeight = 200;
  const finalHeight = 50;
  const scrollDistance = 150;

  window.addEventListener('scroll', () => {
    const scrollY = window.scrollY;
    const scrollPercent = Math.min(1, scrollY / scrollDistance);
    const newHeight = initialHeight - (initialHeight - finalHeight) * scrollPercent;

    header.style.height = `${newHeight}px`;
  });
}