Melhorias na API Web Animations no Chromium 84

Orquestração de animações com promessas, melhorias de desempenho com animações substituíveis, animações mais suaves com modos compostos e muito mais.

Publicado em 27 de maio de 2020

Quando usadas corretamente, as animações melhoram a percepção e a memória do usuário em relação à marca, orientam as ações dele e ajudam a navegar pelo aplicativo, fornecendo contexto em um espaço digital.

A API Web Animations é uma ferramenta que permite aos desenvolvedores escrever animações imperativas com JavaScript. Ele foi criado para apoiar as implementações de animação e transição do CSS e permitir que efeitos futuros sejam desenvolvidos, além de permitir que efeitos atuais sejam compostos e programados.

Embora o Firefox e o Safari já tenham implementado o conjunto completo de recursos das especificações, o Chromium 84 traz uma série de recursos que não eram compatíveis com o Chrome e o Edge, permitindo a interoperabilidade entre navegadores.

A API Web Animations chegou ao Chromium pela primeira vez na versão 36, em julho de 2014. Agora a especificação será concluída na versão 84, que será lançada em julho de 2020.
História da API Web Animations no Chromium.

Primeiros passos

Criar uma animação usando a API Web Animations deve ser muito familiar se você já usou regras @keyframe. Primeiro, você precisa criar um objeto de keyframe. O resultado será algo como este no CSS:

@keyframes openAnimation {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

ficaria assim em JavaScript:

const openAnimation = [
  { transform: 'scale(0)' },
  { transform: 'scale(1)' },
];

Onde você define parâmetros para animação no CSS:

.modal {
  animation: openAnimation 1s 1 ease-in;
}

você definiria no JS:

document.querySelector('.modal').animate(
    openAnimation, {
      duration: 1000, // 1s
      iterations: 1, // single iteration
      easing: 'ease-in' // easing function
    }
);

A quantidade de código é mais ou menos a mesma, mas com o JavaScript, você tem alguns superpoderes que não tem com o CSS sozinho. Isso inclui a capacidade de sequenciar efeitos e um maior controle dos estados de jogo.

Além de element.animate()

No entanto, com a atualização, a API Web Animations não está mais restrita a animações criadas usando element.animate(). Também podemos manipular animações e transições CSS.

getAnimations() é um método que retorna todas as animações em um elemento, independentemente de ter sido criado usando element.animate() ou regras CSS (animação ou transição CSS). Confira um exemplo:

Primeiro, você "get" os frames-chave da transição para determinar de onde estamos fazendo a transição. Em seguida, você cria duas novas animações de opacidade, ativando o efeito de transição cruzada. Quando o crossfade for concluído, você vai excluir a cópia.

Como orquestrar animações com promessas

No Chromium 84, agora há dois métodos que podem ser usados com promessas: animation.ready e animation.finished.

  • O animation.ready permite que você aguarde as mudanças pendentes para que entrem em vigor, ou seja, alternar entre métodos de controle de reprodução, como iniciar e pausar.
  • animation.finished fornece um meio de executar código JavaScript personalizado quando uma animação é concluída.

Continuando com nosso exemplo, crie uma cadeia de animação orquestrada com animation.finished. Aqui, você tem uma transformação vertical (scaleY), seguida por uma transformação horizontal (scaleX), seguida por uma mudança de opacidade em um elemento filho:

Aplicar transformações e opacidade a um elemento modal de abertura. Confira a demonstração no Codepen
const transformAnimation = modal.animate(openModal, openModalSettings);
transformAnimation.finished.then(() => { text.animate(fadeIn, fadeInSettings)});

Encadeamos essas animações usando animation.finished.then() antes de executar o próximo conjunto de animações na cadeia. Dessa forma, as animações aparecem em ordem, e você aplica efeitos a diferentes elementos de destino com opções diferentes definidas, como velocidade e facilidade.

No CSS, isso seria complicado de recriar, especialmente ao aplicar animações únicas, mas sequenciais, a vários elementos. Você precisa usar um @keyframe, classificar as porcentagens de tempo corretas para posicionar as animações e usar animation-delay antes de acionar as animações na sequência.

Exemplo: reproduzir, pausar e reverter

O que pode abrir, precisa fechar! Felizmente, desde o Chromium 39, a API Web Animations permite que nossas animações sejam reproduzidas, pausadas e revertidas.

Você pode usar a animação mostrada anteriormente e dar a ela uma animação suave e invertida ao clicar no botão novamente usando .reverse(). Dessa forma, você pode criar uma interação mais suave e contextual para nosso modal.

Exemplo de abertura e fechamento de uma modal ao clicar no botão. Confira a demonstração no Glitch

O que você pode fazer é criar duas animações em espera de reprodução (openModal e uma transformação de opacidade inline) e pausar uma delas, atrasando-a até que a outra seja concluída. Em seguida, use promessas para aguardar a conclusão de cada uma antes de reproduzir. Por fim, você pode verificar se uma flag está definida e reverter cada animação.

Exemplo: interações dinâmicas com keyframes parciais

Exemplo de segmentação por retargeting, em que um clique do mouse ajusta a animação para um novo local. Confira a demonstração no Glitch
selector.animate([{transform: `translate(${x}px, ${y}px)`}],
    {duration: 1000, fill: 'forwards'});

Neste exemplo, há apenas um frame-chave e nenhuma posição inicial especificada. Este é um exemplo de uso de chaves-frames parciais. O manipulador do mouse faz algumas coisas aqui: ele define um novo local de término e aciona uma nova animação. A nova posição inicial é inferida da posição atual.

Novas transições podem ser acionadas enquanto as atuais ainda estão em execução. Isso significa que a transição atual é interrompida e uma nova é criada.

Melhorias de desempenho com animações substituíveis

Ao criar animações com base em eventos, como em 'mousemove', uma nova animação é criada a cada vez, o que pode consumir memória rapidamente e degradar o desempenho. Para resolver esse problema, animações substituíveis foram introduzidas no Chromium 83, permitindo a limpeza automática, em que as animações concluídas são sinalizadas como substituíveis e removidas automaticamente se substituídas por outra animação concluída. Veja o exemplo a seguir:

Uma trilha de cometa é animada quando o mouse se move. Confira a demonstração no Glitch
elem.addEventListener('mousemove', evt => {
  rectangle.animate(
    { transform: translate(${evt.clientX}px, ${evt.clientY}px) },
    { duration: 500, fill: 'forwards' }
  );
});

Sempre que o mouse se move, o navegador recalcula a posição de cada bola no rastro do cometa e cria uma animação para esse novo ponto. Agora o navegador sabe remover animações antigas (ativando a substituição) quando:

  1. A animação é concluída.
  2. Há uma ou mais animações mais altas na ordem composta que também foram concluídas.
  3. As novas animações estão animando as mesmas propriedades.

É possível conferir exatamente quantas animações estão sendo substituídas somando um contador com cada animação removida, usando anim.onremove para acionar o contador.

Há algumas propriedades e métodos adicionais para melhorar ainda mais o controle de animação:

  • animation.replaceState oferece uma maneira de rastrear se uma animação está ativa, persistente ou removida.
  • animation.commitStyles() atualiza o estilo de um elemento com base no estilo subjacente e em todas as animações no elemento na ordem composta.
  • animation.persist() marca uma animação como não substituível.

Animações mais suaves com modos compostos

Com a API Web Animations, agora é possível definir o modo composto das animações, ou seja, elas podem ser aditiva ou acumulativa, além do modo padrão de "substituir". Os modos compostos permitem que os desenvolvedores programem animações distintas e controlem como os efeitos são combinados. Agora há suporte para três modos compostos: 'replace' (modo padrão), 'add' e 'accumulate'.

Ao compor animações, um desenvolvedor pode escrever efeitos curtos e distintos e combiná-los. No exemplo a seguir, aplicamos um keyframe de rotação e escala a cada caixa, com o único ajuste sendo o modo composto, adicionado como uma opção:

Uma demonstração mostrando os modos compostos padrão, de adição e de acumulação. Confira a demonstração no Glitch

No modo de composição 'replace' padrão, a animação final substitui a propriedade de transformação e termina em rotate(360deg) scale(1.4). Para 'add', o elemento combinável adiciona a rotação e multiplica a escala, resultando em um estado final de rotate(720deg) scale(1.96). 'accumulate' combina as transformações, resultando em rotate(720deg) scale(1.8). Para saber mais sobre as complexidades desses modos compostos, consulte As enumerações CompositeOperation e CompositeOperationOrAuto na especificação de animações da Web.

Confira o exemplo de elemento de interface a seguir:

Um menu suspenso com duas animações compostas aplicadas. Confira a demonstração no Glitch

Aqui, duas animações top são compostas. A primeira é uma macroanimação, que move o menu suspenso pela altura total do menu como um efeito de deslizamento da parte de cima da página. A segunda, uma microanimação, aplica um pequeno salto quando atinge a parte de baixo. O uso do modo composto 'add' permite uma transição mais suave.

const dropDown = menu.animate(
    [
      { top: `${-menuHeight}px`, easing: 'ease-in' },
      { top: 0 }
    ], { duration: 300, fill: 'forwards' });

  dropDown.finished.then(() => {
    const bounce = menu.animate(
      [
        { top: '0px', easing: 'ease-in' },
        { top: '10px', easing: 'ease-out' },
        { ... }
      ], { duration: 300, composite: 'add' });
  });

O que vem por aí na API Web Animations

Essas são adições incríveis aos recursos de animação nos navegadores atuais, e ainda mais novidades estão a caminho. Confira estas especificações futuras para saber mais sobre o que está por vir: