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. Mas existem algumas lacunas nas animações suaves na plataforma web. Eles incluem a capacidade de animar facilmente as animações de entrada e saída, além da animação suave de e para a camada superior em elementos dispensáveis, como caixas de diálogo e pop-overs.

Para preencher essas lacunas, o Chrome 116 e 117 incluem quatro novos recursos da plataforma 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 as transições de propriedades discretas, como display (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, é possível usar display e content-visibility nas regras de frame-chave. Eles serão alterados no momento em que o frame-chave 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 0,5 segundo e, em seguida, 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 ao qual ela foi aplicada 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 gatilho para as animações. 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. Há muitos casos em que convém ir além e remover o nó DOM com um tempo limite para permitir que a animação termine primeiro.

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 possibilita transições discretas 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;
}
(em inglês)
Observação: esta demonstração de transição tem uma técnica diferente da primeira demonstração de animação, mas visualmente é parecida.

A abreviação transition também define esse valor. Assim, 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 distintas, precisará incluir allow-discrete depois de cada uma delas. 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, é necessário 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. Este é o estado "antes de abrir" (de onde você está animando).

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

/*  1. 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;
}

/*  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:

Animar elementos de e para a camada superior

Para animar elementos da camada superior e a partir dela, especifique o @starting-style no estado "aberto" para informar ao navegador de onde a animação será iniciada. 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. BEFORE-OPEN STATE   */
@starting-style {
  dialog[open] {
    translate: 0 100vh;
  }
}

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

/*   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;
}
(link em inglês)

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 um encapsulamento mais visual.

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

.settings-popover {
  &:popover-open {
    /*  0. 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;
    }
    
    /*  1. IS-OPEN STATE  */
    /*  state when popover is open, BOTH:
        what we're transitioning *in* to 
        and transitioning *out* from */
    transform: translateY(0);
    opacity: 1;
  }
  
  /*  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 fazem o escape de clipes e transformações ancestrais e também colocam o conteúdo na camada superior. Se você não fizer a transição de overlay, seu elemento voltará imediatamente a ser recortado, transformado e coberto, e a transição não vai acontecer.

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

Em vez disso, inclua overlay na transição ou animação para animar a overlay com o restante dos recursos e garantir que ela 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;
}
(link em inglês)

Além disso, quando há 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-over ao fazer a transição, ele primeiro será movido para fora da camada superior, pulando para trás do outro pop-over, antes de iniciar a transição. Esse efeito não é 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. Aqui estão 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 da seguinte forma:

Primeiro, no CSS, atribua um view-transition-name individual a cada card.

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

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

/* etc. */

Em seguida, em JavaScript, una a mutação do DOM (nesse caso, removendo o cartão) 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 processa o esmaecimento e a transformação de cada card na nova posição.

Outro exemplo de onde isso pode ser útil é na demonstração de como adicionar/remover itens da 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 ainda mais de animações de entrada e saída suaves na plataforma da Web. Para mais detalhes, confira estes links: