Quatro novos recursos CSS para facilitar a entrada e saída de animações

O movimento é uma parte essencial de qualquer experiência digital, orientando o usuário de uma interação para a próxima. No entanto, há algumas lacunas nas animações suaves na plataforma da Web. Isso inclui a capacidade de animar facilmente animações de entrada e saída e animar suavemente para e da camada superior para elementos dispensáveis, como caixas de diálogo e pop-ups.

Para preencher essas lacunas, o Chrome 116 e o 117 incluem quatro novos recursos da plataforma da Web, que permitem animações e transições suaves para propriedades discretas.

Esses quatro novos recursos incluem:

  • A capacidade de animar display e content-visibility na linha do tempo de um frame-chave (a partir do Chrome 116).
  • A propriedade transition-behavior com a palavra-chave allow-discrete para ativar transições de propriedades discretas, como display (a partir do Chrome 117).
  • A regra @starting-style para animar os efeitos de entrada do display: none para a camada superior (do Chrome 117).
  • A propriedade overlay para controlar o comportamento da camada superior durante uma animação (a partir do Chrome 117).

Exibir animações em frames-chave

No Chrome 116 e versões mais recentes, é possível usar display e content-visibility nas regras de keyframe. Eles serão trocados no momento em que o keyframe ocorrer. Nenhum novo valor adicional é necessário para suportar isso:

.card {
  animation: fade-out 0.5s forwards;
}

@keyframes fade-out {
  100% {
    opacity: 0;
    display: none;
  }
}

O exemplo anterior anima a opacidade para 0 durante a duração de 0,5s e define a exibição como "nenhuma". Além disso, a palavra-chave forwards garante que a animação permaneça no estado final, para que o elemento aplicado permaneça display: none e opacity: 0.

Este é um exemplo simples que imita o que você pode fazer com uma transição (veja a demonstração na seção de transição). No entanto, as transições não podem criar animações mais complexas, como no exemplo abaixo:

.card {
  animation: spin-and-delete 1s ease-in forwards;
}

@keyframes spin-and-delete {
  0% {
    transform: rotateY(0);
    filter: hue-rotate(0);
  }
  80% {
    transform: rotateY(360deg);
    filter: hue-rotate(180deg);
    opacity: 1;
  }
  100% {
    opacity: 0;
    display: none;
  }
}

A animação spin-and-delete é uma animação de saída. Primeiro, o card gira no eixo y, passa por uma rotação de matiz e, em 80% na linha do tempo, faz a transição da opacidade de 1 para 0. Por fim, o cartão muda de display: block para display: none.

Para essas animações de saída, em vez de aplicá-las diretamente a um elemento, você pode configurar um acionador para elas. Por exemplo, anexando um listener de eventos a um botão que aciona uma classe para aplicar a animação, da seguinte forma:

.spin-out {
   animation: spin-and-delete 1s ease-in forwards;
}
document.querySelector('.delete-btn').addEventListener('click', () => {
 document.querySelector('.card').classList.add('spin-out');
})

O exemplo acima agora tem um estado final display:none. Em muitos casos, é necessário ir além e remover o nó DOM com um tempo limite para permitir que a animação seja concluída primeiro.

Como fazer a transição de animações discretas

Ao contrário da animação de propriedades distintas com frames-chave, para fazer a transição de propriedades discretas, você vai precisar usar o modo de comportamento de transição allow-discrete.

A propriedade transition-behavior

O modo allow-discrete é o que torna as transições discretas possíveis e é um valor da propriedade transition-behavior. transition-behavior aceita dois valores: normal e allow-discrete.

.card {
  transition: opacity 0.25s, display 0.25s;
  transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}

.card.fade-out {
  opacity: 0;
  display: none;
}
Observação: essa demonstração de transição mostra uma técnica diferente da primeira demonstração de animação, mas parece visualmente semelhante.

A abreviação transition também define esse valor. Portanto, você pode omitir a propriedade e usar a palavra-chave allow-discrete no final da abreviação transition para cada transição.

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

Se você estiver animando várias propriedades discretas, será necessário incluir allow-discrete após cada propriedade que quiser animar. Exemplo:

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete, overlay 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

A regra @starting-style para animações de entrada

Até agora, este artigo abordou animações de saída. Para criar animações de entrada, você precisa usar a regra @starting-style.

Use @starting-style para aplicar um estilo que o navegador possa procurar antes que o elemento seja aberto na página. Esse é o estado "antes da abertura" (de onde você está animando).

/*  0. IS-OPEN STATE   */
/*  The state at which the element is open + transition logic */
.item {
  height: 3rem;
  display: grid;
  overflow: hidden;
  transition: opacity 0.5s, transform 0.5s, height 0.5s, display 0.5s allow-discrete;
}

/*  1. BEFORE-OPEN STATE   */
/*  Starting point for the transition */
@starting-style {
  .item {
    opacity: 0;
    height: 0;
  }
}

/*  2. EXITING STATE   */
/*  While it is deleting, before DOM removal in JS, apply this
    transformation for height, opacity, and a transform which
    skews the element and moves it to the left before setting
    it to display: none */
.is-deleting {
  opacity: 0;
  height: 0;
  display: none;
  transform: skewX(50deg) translateX(-25vw);
}

Agora você tem um estado de entrada e saída para esses itens da lista de tarefas:

Como animar elementos para e da camada superior

Para animar elementos de e para a camada superior, especifique o @starting-style no estado "aberto" para informar ao navegador de onde a animação vai começar. Para uma caixa de diálogo, o estado aberto é definido com o atributo [open]. Para um pop-over, use a pseudoclasse :popover-open.

Este é um exemplo simples de caixa de diálogo:

/*   0. IS-OPEN STATE   */
dialog[open] {
  translate: 0 0;
}

/*   1. BEFORE-OPEN STATE   */
@starting-style {
  dialog[open] {
    translate: 0 100vh;
  }
}

/*   2. EXIT STATE   */
dialog {
  transition: translate 0.7s ease-out, overlay 0.7s ease-out allow-discrete, display 0.7s ease-out allow-discrete;
  translate: 0 100vh;
}

No próximo exemplo, os efeitos de entrada e saída são diferentes. Entre animando a partir da parte inferior da janela de visualização e saia do efeito para a parte superior da janela de visualização. Ele também é escrito com CSS aninhado para mais encapsulamento visual.

Ao animar um pop-up, use a pseudoclasse :popover-open em vez do atributo open usado anteriormente.

.settings-popover {
  &:popover-open {
    /*  0. IS-OPEN STATE  */
    /*  state when popover is open, BOTH:
        what we're transitioning *in* to 
        and transitioning *out* from */
    transform: translateY(0);
    opacity: 1;

    /*  1. BEFORE-OPEN STATE  */
    /*  Initial state for what we're animating *in* from, 
        in this case: goes from lower (y + 20px) to center  */
    @starting-style {
      transform: translateY(20px);
      opacity: 0;
    }
  }
  
  /*  2. EXIT STATE  */
  /*  Initial state for what we're animating *out* to , 
      in this case: goes from center to (y - 50px) higher */
  transform: translateY(-50px);
  opacity: 0;
  
  /*  Enumerate transitioning properties, 
      including display and allow-discrete mode */
  transition: transform 0.5s, opacity 0.5s, display 0.5s allow-discrete;
}

Propriedade overlay

Por fim, para esmaecer uma popover ou dialog da camada superior, adicione a propriedade overlay à sua lista de transições. popover e dialog escapam de clipes e transformações ancestrais e também colocam o conteúdo na camada superior. Se você não fizer a transição overlay, o elemento vai voltar imediatamente a ser cortado, transformado e coberto, e você não vai notar a transição.

[open] {
  transition: opacity 1s, display 1s allow-discrete;
}

Em vez disso, inclua overlay na transição ou animação para animar overlay com o restante dos recursos e garantir que ele permaneça na camada superior durante a animação. Isso será muito mais suave.

[open] {
  transition: opacity 1s, display 1s allow-discrete, overlay 1s allow-discrete;
}

Além disso, quando você tem vários elementos abertos na camada superior, a sobreposição ajuda a controlar a transição suave para dentro e para fora da camada superior. Confira a diferença neste exemplo simples. Se você não aplicar overlay ao segundo pop-up durante a transição, ele vai sair da camada de cima, pulando para trás do outro pop-up, antes de iniciar a transição. Esse não é um efeito muito suave.

Uma observação sobre as transições de visualização

Se você estiver fazendo alterações no DOM, como adicionar e remover elementos do DOM, outra ótima solução para ter animações suaves são as transições de visualização. Confira dois dos exemplos acima criados usando transições de visualização.

Nesta primeira demonstração, em vez de configurar @starting-style e outras transformações CSS, as transições de visualização vão fazer a transição. A transição de visualização é configurada assim:

Primeiro, no CSS, atribua a cada cartão um view-transition-name individual.

.card-1 {
  view-transition-name: card-1;
}

.card-2 {
  view-transition-name: card-2;
}

/* etc. */

Em seguida, no JavaScript, envolva a mutação do DOM (neste caso, removendo o card) em uma transição de visualização.

deleteBtn.addEventListener('click', () => {
  // Check for browser support
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      // DOM mutation
      card.remove();
    });
  } 
  // Alternative if no browser support
  else {
    card.remove();
  }
})

Agora, o navegador pode processar o desbotamento e a transformação de cada card para a nova posição.

Outro exemplo em que isso pode ser útil é na demonstração de adição/remoção de itens de lista. Nesse caso, você precisa adicionar um view-transition-name exclusivo para cada cartão criado.

Conclusão

Esses novos recursos da plataforma nos aproximam de animações de entrada e saída suaves na plataforma da Web. Para mais detalhes, confira estes links: