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

O movimento é uma parte essencial de qualquer experiência digital, guiando 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 em uma linha do tempo de 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 de display: none e na camada superior (a partir do Chrome 117).
  • A propriedade overlay para controlar o comportamento da camada superior durante uma animação (a partir do Chrome 117).

Mostrar 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. Não são necessários novos valores para oferecer suporte a 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 (confira a demonstração na seção de transição). No entanto, as transições não conseguem criar animações mais complexas, como no exemplo a seguir:

.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 card 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, como esta:

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

Transição de animações discretas

Ao contrário de quando você anima propriedades discretas com keyframes, para fazer a transição de propriedades discretas, é necessário 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 popover, use a pseudoclasse :popover-open.

Um exemplo simples de caixa de diálogo pode ser assim:

/*   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 de baixo para cima na janela de visualização e saia do efeito na parte de cima 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 desfocar um popover ou dialog da camada de cima, adicione a propriedade overlay à 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 vai ficar 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.

Observação sobre as transições de visualização

Se você estiver fazendo mudanças no DOM, como adicionar e remover elementos dele, outra ótima solução para animações suaves é usar 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 do CSS, as transições de visualização vão processar 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: