Orquestrando a complexidade com a API de animações da Web

Publicados: 2022-03-10
Resumo rápido ↬ Existem muitas opções na API de Animações da Web para pegá-las facilmente. Aprender como o tempo funciona e como controlar a reprodução de várias animações ao mesmo tempo cria uma base sólida sobre a qual basear seus projetos.

Não há meio termo entre transições simples e animações complexas. Ou você está bem com o que as transições e animações CSS fornecem ou, de repente, precisa de todo o poder que pode obter. A API de Animações da Web oferece muitas ferramentas para trabalhar com animações. Mas você precisa saber como lidar com eles. Este artigo o guiará pelos principais pontos e técnicas que podem ajudá-lo a lidar com animações complexas enquanto permanece flexível.

Antes de nos aprofundarmos no artigo, é vital que você esteja familiarizado com os conceitos básicos da API de animações da Web e JavaScript. Para deixar claro e evitar distração do problema em mãos, os exemplos de código fornecidos são simples. Não haverá nada mais complexo do que funções e objetos. Como bons pontos de entrada para as próprias animações, eu sugeriria o MDN como referência geral, a excelente série de Daniel C. Wilson e a API CSS Animations vs Web Animations de Ollie Williams. Não vamos percorrer as formas de definir efeitos e ajustá-los para alcançar o resultado desejado. Este artigo pressupõe que você tenha suas animações definidas e precisa de ideias e técnicas para lidar com elas.

Começamos com uma visão geral das interfaces e para que servem. Em seguida, analisaremos o tempo e os níveis de controle para definir o quê, quando e por quanto tempo. Depois disso, aprenderemos a tratar várias animações como uma, envolvendo-as em objetos. Isso seria um bom começo no seu caminho para usar a API de Animações da Web.

Interfaces

A API de Animações da Web nos dá uma nova dimensão de controle. Antes disso, o CSS Transitions and Animation, embora fornecesse uma maneira poderosa de definir efeitos, ainda tinha um único ponto de atuação . Como um interruptor de luz, ou estava ligado ou desligado. Você pode brincar com delays e funções de facilitação para criar efeitos bastante complexos. Ainda assim, em um certo ponto, torna-se complicado e difícil de trabalhar.

A API Web Animations transforma este único ponto de atuação em controle total sobre a reprodução . O interruptor de luz se transforma em um interruptor dimmer com um controle deslizante. Se você quiser, pode transformá-lo em uma casa inteligente, porque além do controle de reprodução agora você pode definir e alterar os efeitos em tempo de execução. Agora você pode adaptar os efeitos ao contexto ou implementar um editor de animações com visualização em tempo real.

Começamos com a interface Animação. Para obter um objeto de animação, podemos usar o método Element.animate . Você fornece quadros-chave e opções e ele reproduz sua animação imediatamente. O que ele também faz é retornar uma instância do objeto Animation . Sua finalidade é controlar a reprodução.

Pense nisso como um toca- fitas , se você se lembra deles. Estou ciente de que alguns dos leitores podem não estar familiarizados com o que é. É inevitável que qualquer tentativa de aplicar conceitos do mundo real para descrever coisas abstratas de computador desmorone rapidamente. Mas deixe-o tranquilizá-lo - um leitor que não conhece a alegria de rebobinar uma fita com um lápis - que as pessoas que sabem o que é um toca-fitas ficarão ainda mais confusas no final deste artigo.

Imagina uma caixa. Tem um slot onde vai a cassete e tem botões para reproduzir, parar e rebobinar. É isso que é a instância da interface Animation — uma caixa que contém uma animação definida e fornece maneiras de interagir com sua reprodução. Você dá a ele algo para jogar e ele devolve os controles.

Os controles que você obtém são convenientemente semelhantes aos que você obtém dos elementos de áudio e vídeo. Eles são métodos de reprodução e pausa e a propriedade de tempo atual . Com esses três controles, você pode construir qualquer coisa quando se trata de reprodução.

O próprio cassete é um pacote que contém uma referência ao elemento que é animado, a definição de efeitos e opções que incluem tempo entre outras coisas. E é isso que o KeyframeEffect é. Nossa fita cassete é algo que contém todas as gravações e informações sobre a duração das gravações. Vou deixar para a imaginação do público mais velho combinar todas essas propriedades com os componentes de um cassete físico. O que vou mostrar é como fica no código.

Ao criar uma animação por meio de Element.animate , você está usando um atalho que faz três coisas. Ele cria uma instância de KeyframeEffect . Ele é colocado em uma nova instância de Animation . Ele imediatamente começa a jogar.

 const animation = element.animate(keyframes, options);

Vamos decompô-lo e ver o código equivalente que faz a mesma coisa.

 const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)

Pegue o cassete (1), coloque-o em um player (2) e aperte o botão Play (3).

O objetivo de saber como funciona nos bastidores é poder separar a definição de quadros-chave e decidir quando reproduzi-lo. Quando você tem muitas animações para coordenar, pode ser útil reuni-las primeiro para que você saiba que elas estão prontas para serem reproduzidas. Gerá-los na hora e esperar que eles comecem a tocar no momento certo não é algo que você gostaria de esperar. É muito fácil quebrar o efeito desejado arrastando alguns quadros. No caso de uma longa sequência, o arrasto se acumula, resultando em uma experiência nada convincente.

Mais depois do salto! Continue lendo abaixo ↓

Tempo

Como na comédia, o timing é tudo nas animações. Para fazer um efeito funcionar, para obter uma certa sensação, você precisa ser capaz de ajustar a maneira como as propriedades mudam. Há dois níveis de tempo que você pode controlar na API de Animações da Web.

Ao nível dos imóveis individuais, offset . O deslocamento oferece controle sobre o tempo de propriedade única . Dando-lhe um valor de zero a um, você define quando cada efeito entra em ação. Quando omitido, é igual a zero.

Você pode se lembrar de @keyframes em CSS como você pode usar porcentagens em vez de from / to . Isso é o que é offset , mas dividido por cem. O valor de offset é uma parte da duração de uma única iteração .

O offset permite organizar quadros-chave em um KeyframeEffect . Ser um deslocamento de número relativo garante que, independentemente da duração ou da taxa de reprodução, todos os seus quadros-chave comecem no mesmo momento em relação uns aos outros.

Como afirmamos anteriormente, o offset é uma parte da duração . Agora eu quero que você evite meus erros e perda de tempo com isso. É importante entender que a duração da animação não é a mesma coisa que a duração geral de uma animação. Normalmente, eles são os mesmos e é isso que pode confundir você, e o que definitivamente me confundiu.

Duração é a quantidade de tempo em milissegundos que uma iteração leva para terminar. Será igual à duração geral por padrão. Depois de adicionar um atraso ou aumentar o número de iterações em uma animação, a duração para de informar o número que você deseja saber. Isso é importante entender para usá-lo a seu favor.

Quando você precisa coordenar a reprodução de um quadro-chave em um contexto maior, como reprodução de mídia, você precisa usar as opções de tempo. A duração total da animação do início ao evento “terminado” na seguinte equação:

 delay + (iterations × duration) + end delay

Você pode vê-lo em ação na demonstração a seguir:

Veja a caneta [Qual é a duração real de uma animação?](https://codepen.io/smashingmag/pen/VwWWrzz) por Kirill Myshkin.

Veja a Caneta Qual é a duração real de uma animação? por Kirill Myshkin.

O que isso nos permite fazer é alinhar várias animações dentro do contexto de mídia de comprimento fixo. Mantendo a duração desejada da animação intacta, você pode “preenchê-la” com delay no início e delayEnd no final para incorporá-la em um contexto com uma duração maior. Se você pensar nisso, o delay nesse sentido agiria como o deslocamento nos quadros-chave. Lembre-se de que o atraso é definido em milissegundos, portanto, convém convertê-lo em um valor relativo.

Mais uma opção de tempo que ajudaria a alinhar a animação é iterationStart . Ele define a posição inicial de uma iteração. Faça a demonstração da bola de bilhar. Ao ajustar o controle deslizante iterationStart você pode definir a posição inicial da bola e a rotação, por exemplo, você pode configurá-lo para começar a pular do centro da tela e fazer com que o número fique reto na câmera no último quadro.

Veja a caneta [Tweak interationStart](https://codepen.io/smashingmag/pen/qBjjVPR) de Kirill Myshkin.

Veja a interação Pen TweakStart por Kirill Myshkin.

Controle vários como um

Quando trabalhei no editor de animação para um aplicativo de apresentação, tive que organizar várias animações para um único elemento em uma linha do tempo. Minha primeira tentativa foi usar o offset para colocar minha animação no ponto inicial certo em uma linha do tempo.

Isso rapidamente provou ser a maneira errada de usar o offset . Em termos dessa animação em movimento de interface do usuário específica na linha do tempo, pretendia mudar sua posição inicial sem alterar a duração da animação. Com o offset isso significava que eu precisava alterar várias coisas, o offset em si e também alterar o offset da propriedade de fechamento para garantir que a duração não seja alterada. A solução provou ser complexa demais para ser compreendida.

O segundo problema veio com a propriedade transform . Devido ao fato de que ele pode representar várias mudanças características em um elemento, pode ser complicado fazê-lo fazer o que você deseja. Em caso de desejo de alterar essas propriedades independentemente umas das outras, isso pode se tornar ainda mais difícil. A função de mudança de escala influencia todas as funções que a seguem. Eis por que isso acontece.

A propriedade Transform pode receber várias funções em uma sequência como valor. Dependendo da ordem da função, o resultado muda. Pegue scale e translate . Às vezes é útil definir a translate em porcentagem, o que significa em relação ao tamanho de um elemento. Digamos que você queira que uma bola salte exatamente três diâmetros próprios de altura. Agora, dependendo de onde você coloca a função de escala - antes ou depois da translate - o resultado muda de três alturas do tamanho original ou dimensionado.

É uma característica importante da propriedade de transform . Você precisa dele para alcançar uma transformação bastante complexa. Mas quando você precisa que essas transformações sejam distintas e independentes de outras transformações de um elemento, isso atrapalha.

Há casos em que você não pode colocar todos os efeitos em uma propriedade de transform . Pode ficar muito rapidamente. Especialmente se seus quadros-chave vierem de lugares diferentes, você precisaria ter uma mesclagem muito complexa de uma string transformada . Você dificilmente poderia confiar em um mecanismo automático porque a lógica não é direta. Além disso, pode ficar difícil entender o que esperar. Para simplificar isso e manter a flexibilidade, precisamos separá-los em diferentes canais.

Uma solução é envolver nossos elementos em div s que cada um pode ser animado separadamente, por exemplo, um div para posicionamento na tela, outro para dimensionamento e um terceiro para rotação. Dessa forma, você não apenas simplifica muito a definição de animações, mas também abre a possibilidade de definir diferentes origens de transformação, quando aplicável.

Pode parecer que as coisas saiam do controle com esse truque. Que estamos multiplicando o número de problemas que tínhamos antes. Na verdade, quando encontrei esse truque pela primeira vez, descartei-o como sendo demais. Eu pensei que poderia apenas garantir que minha propriedade de transform fosse compilada de todas as partes na ordem correta em uma única parte. Foi necessária mais uma função de transform para tornar as coisas muito complexas de gerenciar e certas coisas impossíveis de fazer. Meu compilador de strings de propriedades de transform começou a demorar cada vez mais para acertar, então desisti.

Descobriu-se que controlar a reprodução de várias animações não é tão difícil quanto parece inicialmente. Lembre-se da analogia do toca-fitas desde o início? E se você pudesse usar seu próprio player que aceita qualquer número de cassetes? Mais do que isso, você pode adicionar quantos botões quiser nesse player.

A única diferença entre chamar play em uma única animação e um array de animações é que você precisa iterar. Aqui está o código que você pode usar para qualquer método de instâncias de Animation :

 // To play just call play on all of them animations.forEach((animation) => animation.play());

Usaremos isso para criar todos os tipos de funções para o nosso player.

Vamos criar aquela caixa que conteria as animações e reproduzi-las. Você pode criar essas caixas de qualquer maneira que seja adequada. Para deixar claro, vou mostrar um exemplo de como fazer isso com uma função e um objeto. A função createPlayer recebe uma série de animações que devem ser reproduzidas em sincronia. Ele retorna um objeto com um único método de play .

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }

Isso é o suficiente para você saber para começar a expandir a funcionalidade. Vamos adicionar os métodos pause e currentTime .

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }

O createPlayer com esses três métodos oferece controle suficiente para orquestrar qualquer número de animações . Mas vamos empurrá-lo um pouco mais. Vamos fazer com que nosso player não só possa levar qualquer número de cassetes, mas também outros players.

Como vimos anteriormente, a interface de Animation é semelhante às interfaces de mídia. Usando essa semelhança você pode colocar todo tipo de coisa no seu player. Para acomodar isso, vamos ajustar o método currentTime para fazê-lo funcionar com objetos de animação e objetos que vieram de createPlayer .

 function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }

O player que acabamos de criar é o que permitirá ocultar a complexidade de vários div s para canais de animações de elemento único. Esses elementos podem ser agrupados em uma cena. E cada cena pode ser parte de algo maior. Tudo o que poderia ser feito com esta técnica.

Para demonstrar a demonstração de tempo, dividi todas as animações em três jogadores. A primeira é controlar a reprodução da visualização à direita. O segundo combina animação de salto de todos os contornos das bolas à esquerda e da que está em pré-visualização.

Por fim, o terceiro é um jogador que combinou animações de posição das bolas em um recipiente à esquerda. Esse jogador permite que as bolas se espalhem em uma demonstração contínua da animação com fatias de cerca de 60 quadros por segundo.

Conclusão

Interfaces da Web como a API de Animações da Web expõem para nós certas coisas que os navegadores faziam o tempo todo. Os navegadores sabem como renderizar rapidamente passando o trabalho para a GPU. Com a API Web Animations, temos controle sobre ela. Mesmo que esse controle possa parecer um pouco estranho ou confuso, isso não significa que usá-lo também deva ser confuso. Com uma compreensão do controle de tempo e reprodução, você tem ferramentas para domar essa API de acordo com suas necessidades. Você deve ser capaz de definir o quão complexo deve ser.

Leitura adicional

  • “Técnicas práticas no design de animação”, Sarah Drasner
  • “Projetando com movimento reduzido para sensibilidades de movimento”, Val Head
  • “Uma interface de voz alternativa para assistentes de voz”, Ottomatias Peura
  • “Projetando dicas de ferramentas melhores para interfaces de usuário móveis”, Eric Olive