7 pontos por GN⁺ 2024-05-06 | 1 comentários | Compartilhar no WhatsApp
  • 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 --t em vez de keyframes voltou a ser prática
  • Ao combinar @property --t com uma animação raiz de duração 86400000ms, o tempo avança em milissegundos e pode ser ligado ao cálculo de propriedades como rotate e translate
  • 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() e round(), é possível limitar, repetir, oscilar e suavizar valores de tempo para expressar easing customizado e movimento de posição sem keyframes
  • O css-doodle oferece formas abreviadas como @t, @T e @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 --t para rastrear tempo em milissegundos
  • O exemplo básico declara @property --t como <integer>, altera seu valor de 0 até 86400000 em @keyframes tick e aplica em :root uma animação 86400000ms linear infinite
  • --t aumenta em 1 a cada 1 milissegundo e chega a 1000 após 1 segundo
  • Com counter(), é possível exibir na tela o valor atual de --t
  • Ao ligar cálculos baseados em --t a propriedades, como em rotate: 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)))

Transformando o valor crescente de tempo em valor de animação

  • O valor de --t continua crescendo em uma única direção
  • Valores angle podem ultrapassar 360deg sem 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 com cubic-bezier()
  • Primeiro, limita-se --t a um valor entre 0 e 1
    • --t01: calc(min(1000, var(--t)) / 1000) varia de 0 a 1 ao longo de 1 segundo
  • ease-out-cubic

    • ease-out-cubic é composto pela fórmula 1 - pow(1 - t, 3)
    • Depois de criar o valor --ease-out-cubic, ele é ligado a translate: calc(var(--ease-out-cubic) * 270px)
  • ease-out-elastic

    • ease-out-elastic é composto pela fórmula pow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1
    • Da mesma forma, cria-se --ease-out-elastic e ele é multiplicado em translate para aplicar um deslocamento de 270px

Reduzindo fórmulas com css-doodle

  • Quanto mais complexas as fórmulas ficam, mais var() e calc() dificultam a leitura do código
  • A versão mais recente do css-doodle adicionou a função @t para representar --t, aceitando expressões matemáticas simples diretamente nos argumentos
  • A expressão de rotate pode ser encurtada assim
    • Antes: rotate: calc(mod(var(--t) / 1000, 10) * 5deg)
    • css-doodle: rotate: @t(/1000, %10, *5deg)
  • 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
  • @T e @TS

    • Além de @t, o @T em 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)
  • 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-se rotate: 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

 
GN⁺ 2024-05-06
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: -1500ms inicia a animação imediatamente, mas pula para o ponto de 1,5 segundo
    Controlando 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...

    • Então isso não tornaria possíveis animações no estilo iOS baseadas em offset de rolagem?
  • 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étricas
    CSS 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?

    • Não dá para esquecer que animações CSS são aceleradas por hardware
      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
    • CSS não é uma camada de desenho
      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”
    • Uma vantagem que me vem à mente é a separação entre lógica da aplicação e apresentação
      É 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
    • JS é basicamente single-threaded; claro que existem workers, mas para manter a UI em uma taxa de quadros estável, qualquer coisa que possa ser delegada para outro lugar é bem-vinda
    • Vamos esperar até colocarem WebSocket no CSS!
      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 @property usado 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-value de fallback
    O Chrome usa initial-value quando a variável CSS não foi definida ou foi definida com um valor inválido
    O Firefox usa initial-value apenas quando a variável não foi definida
    Na 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

    • Se quiser testar a demo no Firefox, vá para about:config e ative layout.css.properties-and-values.enabled
    • A forma de definir valores de propriedades a partir do JavaScript quando necessário é esta
      Obter 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 @property ainda não está ativado, isso pode ser usado como um polyfill
  • Curiosamente, no Android Chrome mais recente, as demos anteriores a mod() funcionam mesmo sem flags de recursos experimentais, mas mod() e sin() não fazem nada

  • Eu 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

    • Isso não é simplesmente uma curva de animação do CSS?
  • 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á animada
    Se 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

    • Alguém já usou o RTCode? Parece interessante, mas olhando a homepage fico um pouco confuso se é uma alternativa ao CodePen com suporte em tempo real ou se também pode ser usado em produção