Hackeando o estado da animação CSS e o tempo de reprodução PlatoBlockchain Data Intelligence. Pesquisa vertical. Ai.

Hackeando o estado da animação CSS e o tempo de reprodução

Wolfenstein somente CSS é um pequeno projeto que fiz há algumas semanas. Foi um experimento com transformações e animações CSS 3D.

Inspirado no Demonstração de FPS e outro Wolfenstein Code Pen, decidi construir minha própria versão. É vagamente baseado no Episódio 1 – Piso 9 do jogo original Wolfenstein 3D.

Editor: Este jogo requer intencionalmente alguma reação rápida para evitar uma tela de Game Over.

Aqui está um vídeo de reprodução:

[Conteúdo incorporado]

Em poucas palavras, meu projeto nada mais é do que uma longa animação CSS cuidadosamente roteirizada. Além de alguns exemplos de hack de caixa de seleção.

:checked ~ div { animation-name: spin; }

O ambiente consiste em faces de grade 3D e as animações são principalmente translações e rotações 3D simples. Nada realmente extravagante.

No entanto, dois problemas foram particularmente difíceis de resolver:

  • Reproduza a animação de “disparo de arma” sempre que o jogador clicar em um inimigo.
  • Quando o chefe veloz deu o último golpe, entre em uma câmera lenta dramática.

Em nível técnico, isso significava:

  • Reproduza uma animação quando a próxima caixa de seleção estiver marcada.
  • Desacelere uma animação, quando uma caixa de seleção estiver marcada.

Na verdade, nenhum dos dois foi devidamente resolvido no meu projeto! Ou acabei usando soluções alternativas ou simplesmente desisti.

Por outro lado, depois de algumas pesquisas, finalmente encontrei a chave para ambos os problemas: alterar as propriedades de execução de animações CSS. Neste artigo, vamos explorar mais sobre este tema:

  • Muitos exemplos interativos.
  • Dissecações: como cada exemplo funciona (ou não funciona)?
  • Nos bastidores: como os navegadores lidam com os estados de animação?

Deixe-me “jogar meus tijolos”.

Problema 1: Repetindo a Animação

O primeiro exemplo: “apenas mais uma caixa de seleção”

Minha primeira intuição foi “basta adicionar outra caixa de seleção”, que não funciona:

Cada caixa de seleção funciona individualmente, mas não as duas juntas. Se uma caixa de seleção já estiver marcada, a outra não funcionará mais.

Veja como funciona (ou “não funciona”):

  1. A animation-name of <div> is none por padrão.
  2. O usuário clica em uma caixa de seleção, animation-name torna-se spin, e a animação começa desde o início.
  3. Depois de um tempo, o usuário clica na outra caixa de seleção. Uma nova regra CSS entra em vigor, mas animation-name is ainda spin, o que significa que nenhuma animação é adicionada nem removida. A animação simplesmente continua sendo reproduzida como se nada tivesse acontecido.

O segundo exemplo: “clonar a animação”

Uma abordagem de trabalho é clonar a animação:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }

Veja como funciona:

  1. animation-name is none inicialmente.
  2. O usuário clica em “Girar!”, animation-name torna-se spin1. A animação spin1 é iniciado desde o início porque acabou de ser adicionado.
  3. O usuário clica em “Girar de novo!”, animation-name torna-se spin2. A animação spin2 é iniciado desde o início porque acabou de ser adicionado.

Observe que na Etapa 3, spin1 é removido por causa da ordem das regras CSS. Não funcionará se "Girar de novo!" é verificado primeiro.

O terceiro exemplo: “anexando a mesma animação”

Outra abordagem de trabalho é “anexar a mesma animação”:

#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }

Isso é semelhante ao exemplo anterior. Você pode realmente entender o comportamento desta maneira:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }

Observe que quando "Girar de novo!" estiver marcada, a animação em execução antiga se torna a segunda animação na nova lista, o que pode não ser intuitivo. Uma consequência direta é: o truque não funcionará se animation-fill-mode is forwards. Aqui está uma demonstração:

Se você se pergunta por que isso acontece, aqui estão algumas pistas:

  • animation-fill-mode is none por padrão, o que significa “A animação não tem nenhum efeito se não estiver sendo reproduzida”.
  • animation-fill-mode: forwards; significa “Depois que a animação terminar de ser reproduzida, ela deve permanecer no último quadro-chave para sempre”.
  • spin1a decisão de sempre anula spin2é porque spin1 aparece mais tarde na lista.
  • Suponha que o usuário clique em “Girar!”, espere um giro completo e clique em “Girar de novo!”. Neste momento. spin1 já está finalizado e spin2 apenas começa.

Discussão

Regra geral: você não pode “reiniciar” uma animação CSS existente. Em vez disso, você deseja adicionar e reproduzir uma nova animação. Isso pode ser confirmado por a especificação W3C:

Depois que uma animação é iniciada, ela continua até terminar ou o nome da animação é removido.

Agora, comparando os dois últimos exemplos, acho que na prática, “clonagem de animações” deve funcionar melhor, especialmente quando o pré-processador CSS está disponível.

Problema 2: câmera lenta

Pode-se pensar que desacelerar uma animação é apenas uma questão de definir um animation-duration:

div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }

De fato, isso funciona:

... ou não?

Com alguns ajustes, deve ser mais fácil ver o problema.

Sim, a animação é lenta. E não, não parece bom. O cachorro (quase) sempre “salta” quando você alterna a caixa de seleção. Além disso, o cão parece saltar para uma posição aleatória em vez da inicial. Por quê?

Seria mais fácil entendê-lo se introduzíssemos dois “elementos de sombra”:

Ambos os elementos de sombra estão executando as mesmas animações com diferentes animation-duration. E eles não são afetados pela caixa de seleção.

Quando você alterna a caixa de seleção, o elemento imediatamente alterna entre os estados de dois elementos de sombra.

Citando a especificação W3C:

As alterações nos valores das propriedades da animação durante a execução da animação são aplicadas como se a animação tivesse esses valores desde o início.

Isto segue a sem estado design, que permite que os navegadores determinem facilmente o valor animado. O cálculo real é descrito SUA PARTICIPAÇÃO FAZ A DIFERENÇA e SUA PARTICIPAÇÃO FAZ A DIFERENÇA.

Outra Tentativa

Uma ideia é pausar a animação atual e adicionar uma animação mais lenta que assume a partir daí:

div {
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

Assim funciona:

... ou não?

Ele fica mais lento quando você clica em “Slowmo!”. Mas se você esperar por um círculo completo, verá um “salto”. Na verdade, ele sempre pula para a posição quando “Slowmo!” é clicado.

A razão é que não temos from keyframe definido – e não deveríamos. Quando o usuário clica em “Slowmo!”, spin1 é pausado em alguma posição, e spin2 começa exatamente na mesma posição. Nós simplesmente não podemos prever essa posição de antemão... ou podemos?

Uma solução de trabalho

Podemos! Usando uma propriedade customizada, podemos capturar o ângulo na primeira animação e depois passá-lo para a segunda animação:

div {
  transform: rotate(var(--angle1));
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  transform: rotate(var(--angle2));
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

@keyframes spin1 {
  to {
    --angle1: 360deg;
  }
}

@keyframes spin2 {
  from {
    --angle2: var(--angle1);
  }
  to {
    --angle2: calc(var(--angle1) + 360deg);
  }
}

Observação: @property é usado neste exemplo, que é não suportado por todos os navegadores.

A solução “perfeita”

Há uma ressalva para a solução anterior: “sair do slowmo” não funciona bem.

Aqui está uma solução melhor:

Nesta versão, a câmera lenta pode ser inserida ou encerrada sem problemas. Nenhum recurso experimental é usado também. Então é a solução perfeita? Sim e não.

Esta solução funciona como “mudança” de “engrenagens”:

  • Engrenagens: há duas <div>s. Um é o pai do outro. Ambos têm o spin animação, mas com diferentes animation-duration. O estado final do elemento é o acúmulo de ambas as animações.
  • Deslocamento: No início, apenas um <div> tem sua animação em execução. O outro está pausado. Quando a caixa de seleção é alternada, ambas as animações trocam seus estados.

Embora eu goste muito do resultado, há um problema: é uma boa exploração do spin animação, o que não funciona para outros tipos de animações em geral.

Uma solução prática (com JS)

Para animações gerais, é possível obter a função de câmera lenta com um pouco de JavaScript:

Uma explicação rápida:

  • Uma propriedade personalizada é usada para rastrear o progresso da animação.
  • A animação é “reiniciada” quando a caixa de seleção é alternada.
  • O código JS calcula o correto animation-delay para garantir uma transição perfeita. Eu recomendo Este artigo se você não estiver familiarizado com valores negativos de animation-delay.

Você pode ver esta solução como um híbrido de “reiniciar a animação” e a abordagem de “mudança de marcha”.

Aqui é importante acompanhar o progresso da animação corretamente. Soluções alternativas são possíveis se @property não está disponível. Como exemplo, esta versão usa z-index para acompanhar o andamento:

Nota lateral: originalmente, também tentei criar uma versão somente CSS, mas não consegui. Embora não tenha 100% de certeza, acho que é porque animation-delay is não animável.

Aqui está uma versão com JavaScript mínimo. Apenas “entrar em slowmo” funciona.

Por favor, deixe-me saber se você conseguir criar uma versão funcional somente CSS!

Qualquer animação em câmera lenta (com JS)

Por fim, gostaria de compartilhar uma solução que funciona para (quase) qualquer animação, mesmo com várias @keyframes:

Basicamente, você precisa adicionar um rastreador de progresso de animação e, em seguida, calcular cuidadosamente animation-delay para a nova animação. No entanto, às vezes pode ser complicado (mas possível) obter os valores corretos.

Por exemplo:

  • animation-timing-function não é linear.
  • animation-direction não é normal.
  • vários valores em animation-name com diferentes animation-duration'areia animation-delay'S.

Este método também é descrito SUA PARTICIPAÇÃO FAZ A DIFERENÇA para o API de animações da web.

Agradecimentos

Comecei por esse caminho depois de encontrar projetos somente CSS. Alguns foram arte delicada, e alguns foram engenhocas complexas. Meus favoritos são aqueles que envolvem objetos 3D, por exemplo, este bola quicando e esta cubo de embalagem.

No começo, eu não tinha ideia de como eles eram feitos. Mais tarde eu li e aprendi muito com este belo tutorial da Ana Tudor.

Como se viu, construir e animar objetos 3D com CSS não é muito diferente de fazê-lo com liqüidificador, apenas com um sabor um pouco diferente.

Conclusão

Neste artigo examinamos o comportamento de animações CSS quando um animate-* propriedade é alterada. Especialmente desenvolvemos soluções para “reproduzir uma animação” e “animação em câmera lenta”.

Espero que você ache este artigo interessante. Por favor, deixe-me saber seus pensamentos!

Carimbo de hora:

Mais de Truques CSS