Técnica de animação CSS baseada em tempo
(yuanchuan.dev)- Com o aumento do suporte a funções matemáticas em CSS, a abordagem de calcular o estado da animação com a variável de tick de tempo
--tem vez de keyframes voltou a ser prática - Ao combinar
@property --tcom uma animação raiz de duração86400000ms, o tempo avança em milissegundos e pode ser ligado ao cálculo de propriedades comorotateetranslate - Com
step(), é possível limitar a taxa de quadros a 8fps, 24fps ou 60fps, e graças às otimizações do navegador, frequências de atualização acima de 60fps normalmente não causam problemas - Com
min(),mod(),sin(),pow()eround(), é possível limitar, repetir, oscilar e suavizar valores de tempo para expressar easing customizado e movimento de posição sem keyframes - O
css-doodleoferece formas abreviadas como@t,@Te@TS, permitindo escrever demos com muitas fórmulas de forma mais curta e experimentar mais rápido
Transformando ticks de tempo em uma variável CSS
- Em abordagens antigas, era difícil fazer cálculos matemáticos complexos em CSS, o que limitava o alcance da técnica
- Hoje, o CSS oferece suporte às funções matemáticas necessárias, como
mod(),round()e funções trigonométricas, tornando viável tentar novamente animações baseadas em tempo - Para ver os demos, pode ser necessário ativar Experimental feature flags
Funcionamento básico
- O CSS não consegue iniciar um timer diretamente como o JavaScript, mas com a CSS Houdini API é possível definir a variável customizada
--tpara rastrear tempo em milissegundos - O exemplo básico declara
@property --tcomo<integer>, altera seu valor de0até86400000em@keyframes ticke aplica em:rootuma animação86400000ms linear infinite --taumenta em1a cada 1 milissegundo e chega a1000após 1 segundo- Com
counter(), é possível exibir na tela o valor atual de--t - Ao ligar cálculos baseados em
--ta propriedades, como emrotate: calc(var(--t) * .001turn), o valor muda com o tempo e isso se torna uma animação
Controle da taxa de quadros
- Para animações suaves, uma frequência de atualização de 60fps é suficiente
- Como há otimizações de renderização no navegador, frequências acima de 60fps normalmente não causam problemas
- Se necessário, é possível limitar diretamente a taxa de quadros com step() em
animation-timing-function- 8fps:
step(calc(86400000/(1000/8))) - 24fps:
step(calc(86400000/(1000/24))) - 60fps:
step(calc(86400000/(1000/60)))
- 8fps:
Transformando o valor crescente de tempo em valor de animação
- O valor de
--tcontinua crescendo em uma única direção - Valores
anglepodem ultrapassar360degsem problema, mas nem todas as propriedades CSS tratam valores de forma cíclica translate: calc(var(--t) * .001px)continua movendo a caixa para a direita-
Parando com
min()- Para parar assim que um certo deslocamento for alcançado, é possível usar min()
translate: min(270px, calc(var(--t) * .5px))limita o movimento a 270px- Para controlar a duração com precisão, é possível limitar o próprio
--t translate: calc(min(3000, var(--t)) * (270px / 3000))- Esse cálculo faz o elemento se mover 270px ao longo de 3 segundos
-
Repetindo com
mod()- Para fazer a caixa ir para a direita e depois recomeçar do início, usa-se mod()
translate: calc(mod(var(--t)/4, 270) * 1px)permite repetir o deslocamento
-
Criando movimento de vai e vem com
sin()- O movimento de ida e volta pode ser expresso com sin()
- O valor de tempo é ligado à função trigonométrica em algo como
translate: calc(sin(mod(var(--t)/135, 270)) * 135px)
Funções de easing customizadas
- Com funções matemáticas do CSS e a variável
--t, é possível criar easings customizados que podem ser difíceis de alcançar comcubic-bezier() - Primeiro, limita-se
--ta um valor entre0e1--t01: calc(min(1000, var(--t)) / 1000)varia de 0 a 1 ao longo de 1 segundo
-
ease-out-cubicease-out-cubicé composto pela fórmula1 - pow(1 - t, 3)- Depois de criar o valor
--ease-out-cubic, ele é ligado atranslate: calc(var(--ease-out-cubic) * 270px)
-
ease-out-elasticease-out-elasticé composto pela fórmulapow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1- Da mesma forma, cria-se
--ease-out-elastice ele é multiplicado emtranslatepara aplicar um deslocamento de 270px
Reduzindo fórmulas com css-doodle
- Quanto mais complexas as fórmulas ficam, mais
var()ecalc()dificultam a leitura do código - A versão mais recente do css-doodle adicionou a função
@tpara representar--t, aceitando expressões matemáticas simples diretamente nos argumentos - A expressão de
rotatepode ser encurtada assim- Antes:
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) css-doodle:rotate: @t(/1000, %10, *5deg)
- Antes:
- Mesmo sem escrever keyframes, é possível criar animações baseadas em matemática com código curto
- Ao mudar os parâmetros, dá para testar rapidamente novos resultados
-
@Te@TS- Além de
@t, o@Tem maiúsculas representa os time ticks desde o início do dia @TSé um shorthand de@t(/1000)e rastreia o tempo em segundos- O exemplo de relógio pode ser visto no CodePen
- Ponteiro dos segundos:
rotate: @TS(*6, %360deg) - Ponteiro dos minutos:
rotate: @TS(/60, *6, %360deg) - Ponteiro das horas:
rotate: @TS(/60, /12, *6, %360deg)
- Além de
-
Criando o salto do ponteiro dos segundos com
round()- Para fazer o ponteiro dos segundos se mover em saltos, usa-se round()
- O terceiro parâmetro de
round()define o intervalo de arredondamento - No relógio, um passo é
360 / 60 = 6deg, então usa-serotate: round(down, @TS(*6, %360deg), 6deg)
Demos e pontos de aplicação
- Um exemplo que anima cor e posição ao mesmo tempo pode ser visto no CodePen
- Um exemplo de animação de cor de texto está no CodePen
- Keyframes podem parecer mais intuitivos, mas em cenas de demo com muitos cálculos matemáticos e variáveis de entrada, a abordagem de usar o tempo como variável facilita criar resultados mais variados
- Em uma atualização de 17 de maio de 2024, foi adicionado o demo de animação de cor de texto
1 comentários
Comentários no Hacker News
Se você quiser controlar o progresso via JS, também recomendo usar um valor negativo de animation-delay
Por exemplo,
animation-delay: -1500msinicia a animação imediatamente, mas pula para o ponto de 1,5 segundoControlando esse valor via JS, dá para praticamente “esfregar” uma animação CSS a partir do JS, alinhando todas as animações a um loop de tick no estilo compute-update-render de um motor de jogo
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...
Se ficar só um pouco mais complexo do que uma função de easing simples ou keyframes básicos em um ou dois canais, você logo esbarra nos limites dessa abordagem
Tenho usado Theatre.js nos últimos anos e estou bastante satisfeito
É uma biblioteca dividida entre uma UI de estúdio para editar keyframes e curvas de Bézier em uma timeline, e um runtime que interpola esses keyframes com base na timeline
Se você precisa sincronizar várias animações, vale experimentar
https://www.theatrejs.com/
É muito impressionante, mas de algum modo também dá a sensação de que só agora estamos chegando perto do que era possível com Flash décadas atrás
Não estou dizendo que quero a volta do Flash, mas gostaria que houvesse ferramentas mais amigáveis para criar animações CSS
CSS agora tem suporte suficiente a funções matemáticas como
mod(),round()e funções trigonométricasCSS não consegue iniciar timers como JavaScript, mas hoje dá para definir variáveis customizadas que acompanham o tempo em milissegundos usando a API CSS Houdini
Mas, se já existe JavaScript, por que tudo isso seria necessário?
A camada de desenho não deveria se concentrar nos primitivos de desenho? Por que colocar nela mais recursos de alto nível?
Se você estiver rodando funções trigonométricas em JS para uma animação, provavelmente também está manipulando o DOM para tentar atualizar a UI a cada milissegundo, e esse custo é muito alto
O desenho é conduzido pelo DOM, que é composto por HTML ou atualizado por JS
CSS apenas fica por cima, passando parâmetros ao motor de pintura sobre o que fazer com o DOM e sobrescrevendo os valores padrão
Ele também pode adicionar blocos ao DOM, alterar o comportamento de posicionamento com algoritmos complexos, e até margens na verdade não são tão simples assim
Não vejo isso como uma ótima solução, mas parece ter começado como uma forma de evitar atualizar o código HTML para mudar cores durante a renderização do DOM
Provavelmente porque atualizar código HTML não era nada divertido, mas acho que resolveram o problema errado desde o início
Também havia o fato de ser a era dos “webmasters”
É mais fácil de ler quando a lógica de fetch não fica misturada com a lógica geométrica de progresso de um elemento DOM de loader, como calcular o ângulo de rotação e aplicar estilos inline
Sei que hoje em dia não está na moda manter chocolate e manteiga de amendoim separados, mas convenções assim criam limites que facilitam inferir onde o código estará e onde deve ser colocado
Brincadeiras à parte, CSS é declarativo, e acho natural haver sobreposição entre problemas que podem ser resolvidos com JS e CSS
Estou realmente animado com os novos recursos adicionados recentemente ao CSS, e é bom ter menos código interpretado
O CSS customizado
@propertyusado no artigo é excelente, e o suporte dos navegadores também está em 88% [1]Mas é preciso tomar cuidado com diferenças de comportamento entre navegadores ao definir um
initial-valuede fallbackO Chrome usa
initial-valuequando a variável CSS não foi definida ou foi definida com um valor inválidoO Firefox usa
initial-valueapenas quando a variável não foi definidaNa maioria dos projetos isso não será problema, mas em um projeto recente precisei definir o valor padrão via JavaScript no Firefox para alinhar as diferenças de implementação entre navegadores
[1] https://caniuse.com/?search=%40property
about:confige ativelayout.css.properties-and-values.enabledObter o valor de uma propriedade definida em
:root{}:getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");Definir o valor da propriedade:
document.documentElement.style.setProperty("--some-custom-css-var", "some value");Em navegadores nos quais o suporte a CSS
@propertyainda não está ativado, isso pode ser usado como um polyfillCuriosamente, no Android Chrome mais recente, as demos anteriores a
mod()funcionam mesmo sem flags de recursos experimentais, masmod()esin()não fazem nadaEu queria isso há um tempo porque queria criar algo como box breathing
Queria poder ajustar as etapas de inspirar-pausar-expirar-pausar ao corpo da pessoa e ao efeito desejado
Sem algo como a abordagem desta página, não encontrei um jeito de implementar sem JS, ou sem uma complexidade absurda
Meu Deus, em uma folha de estilos! As demos desta página são realmente criativas
O artigo é muito bem escrito
O Chrome ainda não lançou oficialmente suporte a CSS
mod(), então, se você não estiver usando uma versão preview do Chrome, a maioria dos exemplos da página não será animadaSe você não precisa de controle por tick e só quer animações CSS suaves em todos os navegadores, a técnica FLIP é útil
https://medium.com/outsystems-experts/flip-your-60-fps-anima...
A animação no rodapé da página é muito impressionante
Parece mais um vídeo do que elementos individuais
https://RTCode.io é um playground web em tempo real que funciona sem atualizar a página e sem reinicializar o estado, então pode ajudar muito quem trabalha com animações ou transições em CSS
A demo está em https://RTCode.io
Para importar uma Pen, basta trocar o hostname da URL para
xcodepen.io