Adicionar efeitos de entrada e saída aos elementos conforme eles entram ou saem da janela de rolagem

Os efeitos de entrada e saída são animações acionadas quando um elemento entra ou sai da janela de visualização. Isso pode ser usado para criar experiências de usuário envolventes e dinâmicas. Por exemplo, você pode usar um efeito de entrada para fazer um elemento aparecer gradualmente ao rolar para a visualização ou um efeito de saída para reduzir o tamanho dele ao rolar para fora da visualização.

Como fazer a implementação

Para adicionar efeitos de entrada e saída a um elemento, é necessário combinar algumas propriedades CSS. Veja a seguir um guia passo-a-passo:

  1. Crie @keyframes separados para as animações de entrada e saída. A animação de entrada será aplicada quando o elemento entrar na janela de visualização, e a de saída, quando ele sair.

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. Anexe os frames-chave de entrada e saída ao elemento. Para fazer isso, defina várias animações na propriedade animation.

    • Dê à animação de entrada um animation-fill-mode de backwards para que ela aplique o estado inicial antes de começar.
    • Dê à animação de saída um animation-fill-mode de forwards para que ela mantenha o estado final após a conclusão.
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. Crie uma linha do tempo de visualização e vincule-a às animações. Uma linha do tempo de visualização é um tipo de linha do tempo vinculada à visibilidade de um elemento na janela de visualização. Você pode criar um usando a função view() e aplicá-lo às suas animações usando a propriedade animation-timeline.

    .animated-element {
      animation-timeline: view();
    }
    

    Por padrão, view() rastreia o elemento no eixo block. Se você precisar rastreá-lo no eixo inline, use view(inline).

  4. Limite as animações aos intervalos entry e exit. A propriedade animation-range permite especificar em qual parte da linha do tempo uma animação deve ser executada.

    • O intervalo entry abrange o tempo desde que o elemento entra na janela de visualização até que ele esteja totalmente visível.
    • O intervalo exit abrange o tempo desde o momento em que o elemento começa a sair da janela de visualização até que ele fique completamente oculto.
    .animated-element {
      animation-range: entry, exit;
    }
    

Exemplo de código

Este código anima os filhos diretos do scroller ao rolar usando uma linha do tempo de visualização anônima:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

Quando os elementos entram na janela de rolagem, a animação grow é reproduzida. Quando eles saem, a animação shrink é reproduzida.

O código a seguir tem o mesmo resultado visual, mas anima os filhos diretos do scroller ao rolar usando uma linha do tempo de visualização nomeada:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

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: view()) and (animation-range: entry)) para verificar a compatibilidade e fornecer um substituto para navegadores que não oferecem suporte.
    • A verificação (animation-range: entry) 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.
  • 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 view() para criar uma animação acionada por rolagem:

  • 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 view(block) ou view(inline).
  • Quando a animação não é aplicada ao próprio objeto rastreado, use uma linha do tempo de visualização nomeada.

Ao usar a propriedade view-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 view-timeline-axis.
  • VERIFIQUE se o escopo da pesquisa funciona: quando o elemento que declara o view-timeline não é um ancestral de árvore simples do elemento animado, eleve a visibilidade do nome do view-timeline usando timeline-scope em um ancestral compartilhado.

Dê preferência a um view-timeline nomeado quando vários elementos ou filhos do objeto rastreado precisarem ser animados.

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 IntersectionObserver para rastrear a visibilidade do elemento .wrapper e atualiza a propriedade transform das camadas com base na posição de rolagem.

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>