Criar efeitos de slide de carrossel

Os efeitos de slide são uma ótima maneira de deixar um carrossel mais interessante. Conforme o usuário rola os slides, cada um deles pode ser animado ao entrar, centralizar e sair da porta de rolagem. Por exemplo, os slides podem aparecer e desaparecer gradualmente, girar ou mudar de tamanho. Isso cria uma experiência do usuário dinâmica e envolvente. Ao contrário das animações simples de entrada/saída, esse efeito usa uma única animação contínua para controlar a aparência do slide em toda a porta de rolagem.

Como fazer a implementação

Veja como criar efeitos de slide em carrossel:

  1. Crie um scroller: esse elemento vai funcionar como o contêiner dos slides do carrossel. Neste exemplo, ele usa overflow-x: scroll para permitir a rolagem horizontal.

    <ul class="scroller">
      <li class="entry">1</li>
      <li class="entry">2</li>
      <li class="entry">3</li>
      …
    </ul>
    
    .scroller {
      overflow-x: scroll;
    }
    
  2. Defina a animação: crie uma animação CSS que defina os diferentes estados dos slides conforme eles atravessam a porta de rolagem. É possível definir frames-chave para qualquer parte da animação. Por exemplo, é possível definir um estado para quando os slides estiverem no centro da porta de rolagem incluindo um frame-chave 50%. Neste exemplo, a propriedade scale faz com que os slides aumentem à medida que se aproximam do centro e diminuam à medida que se afastam.

    @keyframes animate {
      0% {
        scale: 0.5;
      }
      50% {
        scale: 1;
      }
      100% {
        scale: 0.5;
      }
    }
    
  3. Aplique a animação e o view-timeline: anexe a animação aos slides do carrossel e vincule-a a um view-timeline que rastreie o elemento conforme ele rola pelo contêiner.

    .scroller > * {
      animation: animate auto linear both;
      animation-timeline: view(inline);
    }
    

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

Exemplo de código

Este código anima os itens do carrossel de um scroller horizontal usando uma linha do tempo de visualização anônima:

@keyframes animate {
  0% {
    scale: 0.5;
  }

  50% {
    scale: 1;
  }

  100% {
    scale: 0.5;
  }
}

.scroller > * {
  /* Applies the animation using an `auto` duration */
  animation: animate auto linear both;
  /* Sets the animation timeline to use an anonymous view progress timeline, tracking the element's progress through the scroller on the inline axis */
  animation-timeline: view(inline);
}

Este código anima os itens de um carrossel de rolagem horizontal usando uma linha do tempo de visualização nomeada:

@keyframes animate {
  0% {
    scale: 0.5;
  }

  50% {
    scale: 1;
  }

  100% {
    scale: 0.5;
  }
}

/* This creates a named view-timeline on each carousel item. The timeline is used to drive the animation that is applied on the same element. */
.scroller > * {
  /* Applies the animation using an `auto` duration */
  animation: animate auto linear both;
  /* Defines a named view progress timeline, tracking the element's progress through the scroller on the inline axis */
  view-timeline: --item inline;
  /* Sets the animation timeline to use the named view progress timeline defined above */
  animation-timeline: --item;
}

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

Prefira um view-timeline nomeado quando vários elementos do DOM precisarem ser animados com base na mesma linha do tempo ou quando você precisar animar filhos do elemento que tem o view-timeline definido. Se o elemento que você anima também é o que define o view-timeline, use uma linha do tempo de visualização anônima com view().

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

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

  • USE um CSS <dashed-ident> para o nome (por exemplo, view-timeline: --my-custom-name)
  • 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.

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 a API Web Animations (Element.animate()) para criar uma animação pausada para cada item no carrossel. Em seguida, ele detecta o evento scroll no scroller e atualiza o currentTime de cada animação com base no progresso de rolagem do item dentro do scroller.

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
  const scroller = document.querySelector('.scroller');
  const entries = document.querySelectorAll('.entry');

  // Create a map to store animations
  const animations = new Map();

  entries.forEach(entry => {
    const animation = entry.animate(
      {
        scale: ['0.5', '1', '0.5']
      },
      {
        duration: 1, // We'll control the time ourselves
        fill: 'both'
      }
    );
    animation.pause();
    animations.set(entry, animation);
  });

  // Update animations on scroll
  const tick = () => {
    const scrollerRect = scroller.getBoundingClientRect();

    entries.forEach(entry => {
      const animation = animations.get(entry);
      if (!animation) return;

      const entryRect = entry.getBoundingClientRect();
      const progress = (entryRect.left + entryRect.width / 2 - scrollerRect.left) / scrollerRect.width;

      animation.currentTime = progress;
    });
  };
    
  scroller.addEventListener('scroll', tick);
  tick();
}