7 puntos por GN⁺ 2024-05-06 | 1 comentarios | Compartir por WhatsApp
  • A medida que crece el soporte de funciones matemáticas en CSS, vuelve a ser práctico un enfoque que calcula el estado de la animación con la variable de ticks de tiempo --t en lugar de keyframes
  • Al combinar @property --t con una animación raíz de duración 86400000ms, el tiempo aumenta en milisegundos y puede vincularse al cálculo de propiedades como rotate y translate
  • Con step() se puede limitar la tasa de fotogramas a 8fps, 24fps o 60fps, y gracias a las optimizaciones del navegador, una frecuencia de actualización superior a 60fps normalmente no causa problemas
  • Con min(), mod(), sin(), pow() y round() se puede limitar, repetir, hacer oscilar y suavizar el valor del tiempo para expresar easing personalizado y movimiento de posición sin keyframes
  • css-doodle ofrece expresiones abreviadas como @t, @T y @TS, lo que permite escribir demos con muchas fórmulas de forma más corta y experimentar más rápido

Convertir un tick de tiempo en una variable CSS

  • En el pasado, este enfoque tenía un alcance limitado porque era difícil hacer cálculos matemáticos complejos en CSS
  • Hoy CSS soporta las funciones matemáticas necesarias, como mod(), round() y funciones trigonométricas, así que se puede volver a intentar la animación basada en tiempo
  • Para ver la demo, puede ser necesario activar las Experimental feature flags

Funcionamiento básico

  • CSS no puede iniciar un temporizador directamente como JavaScript, pero con la API CSS Houdini se puede definir una variable personalizada --t para seguir el tiempo en milisegundos
  • El ejemplo básico declara @property --t como <integer>, la cambia de 0 a 86400000 en @keyframes tick, y aplica en :root una animación 86400000ms linear infinite
  • --t aumenta en 1 cada milisegundo y llega a 1000 en 1 segundo
  • Con counter() se puede mostrar en pantalla el valor actual de --t
  • Si se conecta un cálculo basado en --t a una propiedad, como rotate: calc(var(--t) * .001turn), el valor cambia con el paso del tiempo y se convierte en una animación

Control de la tasa de fotogramas

  • Para una animación fluida, una frecuencia de actualización de 60fps es suficiente
  • Gracias a las optimizaciones de renderizado del navegador, una frecuencia superior a 60fps normalmente no representa un problema
  • Si hace falta, se puede limitar directamente la tasa de fotogramas usando step() en animation-timing-function
    • 8fps: step(calc(86400000/(1000/8)))
    • 24fps: step(calc(86400000/(1000/24)))
    • 60fps: step(calc(86400000/(1000/60)))

Convertir un valor de tiempo creciente en un valor de animación

  • El valor --t sigue aumentando en una sola dirección
  • Un valor angle puede superar 360deg sin problema, pero no todas las propiedades CSS tratan sus valores de forma cíclica
  • translate: calc(var(--t) * .001px) hace que una caja se siga moviendo hacia la derecha
  • Detener con min()

    • Si se quiere que se detenga apenas llegue a un offset específico, se puede usar min()
    • translate: min(270px, calc(var(--t) * .5px)) limita el valor del movimiento a 270px
    • Si se quiere controlar con precisión la duración, se puede limitar --t en sí mismo
    • translate: calc(min(3000, var(--t)) * (270px / 3000))
    • Este cálculo hace que se desplace 270px durante 3 segundos
  • Repetir con mod()

    • Si se quiere que la caja se mueva a la derecha y luego vuelva a empezar desde el inicio, se usa mod()
    • translate: calc(mod(var(--t)/4, 270) * 1px) permite repetir el offset
  • Crear movimiento de ida y vuelta con sin()

    • El movimiento de ida y vuelta se puede expresar con sin()
    • translate: calc(sin(mod(var(--t)/135, 270)) * 135px) conecta el valor del tiempo a una función trigonométrica

Funciones de easing personalizadas

  • Con las funciones matemáticas de CSS y la variable --t se pueden crear easing personalizados que podrían ser difíciles de lograr con cubic-bezier()
  • Primero se limita --t a un valor entre 0 y 1
    • --t01: calc(min(1000, var(--t)) / 1000) cambia de 0 a 1 en 1 segundo
  • ease-out-cubic

    • ease-out-cubic se compone de la fórmula 1 - pow(1 - t, 3)
    • Después de crear el valor --ease-out-cubic, se conecta a translate: calc(var(--ease-out-cubic) * 270px)
  • ease-out-elastic

    • ease-out-elastic se compone de la fórmula pow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1
    • Del mismo modo, se crea el valor --ease-out-elastic y se multiplica en translate para aplicarlo a un movimiento de 270px

Reducir fórmulas con css-doodle

  • A medida que las fórmulas se vuelven más complejas, var() y calc() hacen que el código sea más difícil de leer
  • La versión más reciente de css-doodle agregó la función @t para representar --t, y dentro de sus argumentos puede recibir directamente expresiones matemáticas simples
  • La expresión de rotate se acorta así
    • Antes: rotate: calc(mod(var(--t) / 1000, 10) * 5deg)
    • css-doodle: rotate: @t(/1000, %10, *5deg)
  • Sin escribir keyframes, se pueden crear animaciones basadas en matemáticas con código breve
  • Al cambiar los parámetros, se pueden probar nuevos resultados rápidamente
  • @T y @TS

    • Además de @t, la versión en mayúscula @T representa los ticks de tiempo desde el inicio del día
    • @TS es la shorthand de @t(/1000) y sigue el tiempo en segundos
    • El ejemplo del reloj puede verse en CodePen
    • Segundero: rotate: @TS(*6, %360deg)
    • Minutero: rotate: @TS(/60, *6, %360deg)
    • Horario: rotate: @TS(/60, /12, *6, %360deg)
  • Crear el salto del segundero con round()

    • Si se quiere que el segundero se mueva a saltos, se puede usar round()
    • El tercer parámetro de round() especifica el intervalo de redondeo
    • En un reloj, un paso es 360 / 60 = 6deg, por eso se usa rotate: round(down, @TS(*6, %360deg), 6deg)

Demos y puntos de aplicación

  • Un ejemplo que anima color y posición al mismo tiempo puede verse en CodePen
  • Un ejemplo de animación de color de texto está en CodePen
  • Aunque keyframes puede parecer más intuitivo, en escenas demo con muchos cálculos matemáticos y variables de entrada, usar el tiempo como variable facilita crear resultados más variados
  • En la actualización del 17 de mayo de 2024 se agregó la demo de animación de color de texto

1 comentarios

 
GN⁺ 2024-05-06
Opiniones en Hacker News
  • Si quieres controlar el progreso con JS, también recomiendo usar un valor negativo de animation-delay.
    Por ejemplo, animation-delay: -1500ms inicia la animación de inmediato, pero salta al punto de 1.5 segundos.
    Si controlas ese valor con JS, básicamente puedes hacer scrubbing de animaciones CSS desde JS, y sincronizar todas las animaciones con un loop de ticks al estilo de un motor de juegos: compute-update-render.
    https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...

    • ¿Entonces no se podrían hacer también animaciones basadas en el offset de scroll al estilo iOS?
  • En cuanto te sales un poco de una función de easing simple o de keyframes básicos con uno o dos canales, te topas rápido con los límites de este enfoque.
    Durante los últimos años he usado Theatre.js y estoy bastante satisfecho.
    Es una biblioteca dividida en una UI de estudio para editar keyframes y curvas Bézier en una línea de tiempo, y un runtime que interpola esos keyframes con base en la línea de tiempo.
    Si necesitas coordinar varias animaciones, vale la pena probarla.
    https://www.theatrejs.com/

  • Es muy impresionante, pero de alguna forma también se siente como si apenas ahora nos estuviéramos acercando a lo que era posible con Flash hace décadas.
    No es que quiera que Flash vuelva, pero sí me gustaría que hubiera herramientas más amigables para crear animaciones CSS.

  • CSS ya tiene suficiente soporte para funciones matemáticas como mod(), round() y funciones trigonométricas.
    CSS no puede iniciar timers como JavaScript, pero hoy en día con la API de CSS Houdini se pueden definir variables personalizadas que rastrean el tiempo en milisegundos.
    Pero si existe JavaScript, ¿por qué hace falta todo esto?
    ¿No debería la capa de dibujo enfocarse en las primitivas de dibujo? ¿Por qué meterle más funciones de alto nivel?

    • No hay que olvidar que las animaciones CSS están aceleradas por hardware.
      Si estás ejecutando funciones trigonométricas en JS para una animación, probablemente también estés manipulando el DOM para actualizar la UI cada milisegundo, y ese costo es muy alto.
    • CSS no es la capa de dibujo.
      El dibujo lo impulsa el DOM, que se compone con HTML o se actualiza con JS.
      CSS solo se monta encima pasando parámetros al motor de pintado sobre qué hacer con el DOM y sobrescribiendo valores predeterminados.
      También puede agregar bloques del DOM, cambiar el comportamiento de posicionamiento con algoritmos complejos, e incluso los márgenes no son tan simples en realidad.
      No lo veo como una gran solución, pero parece que empezó como una forma de no tener que actualizar código HTML para cambiar colores durante el renderizado del DOM.
      Probablemente porque actualizar código HTML no era nada divertido, aunque creo que desde el inicio resolvieron el problema equivocado.
      También hay que considerar que era la época de los “webmasters”.
    • Una ventaja que se me ocurre es la separación entre lógica de aplicación y presentación.
      Es más fácil de leer si dentro de la lógica de fetch no se mezcla la lógica de progresión geométrica de un elemento DOM de loader, por ejemplo calcular el ángulo de rotación y aplicar estilos inline.
      Sé que hoy en día no está de moda mantener separados el chocolate y la mantequilla de maní, pero estas convenciones trazan límites que hacen más fácil deducir dónde estará el código y dónde debería ir.
    • JS es básicamente single-threaded; claro, existen los workers, pero si quieres mantener la UI con una tasa de cuadros estable, cualquier cosa que puedas delegar a otro lado ayuda.
    • ¡Esperemos a que CSS tenga WebSocket!
      Bromas aparte, CSS es declarativo y me parece natural que haya problemas que se puedan resolver tanto con JS como con CSS.
      Las nuevas funciones que se están agregando últimamente a CSS me emocionan mucho, y que haya menos código interpretado es algo bueno.
  • El @property personalizado de CSS que se usó en el artículo es excelente, y el soporte de navegadores ya es de 88% [1].
    Eso sí, hay que tener cuidado con diferencias de comportamiento entre navegadores al establecer un initial-value de respaldo.
    Chrome usa initial-value cuando una variable CSS no está definida o está establecida con un valor inválido.
    Firefox usa initial-value solo cuando la variable no está definida.
    En la mayoría de los proyectos no será un problema, pero en un proyecto reciente tuve que establecer el valor predeterminado con JavaScript en Firefox para igualar las diferencias de implementación entre navegadores.
    [1] https://caniuse.com/?search=%40property

    • Si quieres probar la demo en Firefox, ve a about:config y activa layout.css.properties-and-values.enabled.
    • Para establecer el valor de una propiedad desde JavaScript cuando haga falta, se hace así:
      Obtener el valor de una propiedad definida en :root{}:
      getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");
      Establecer el valor de una propiedad:
      document.documentElement.style.setProperty("--some-custom-css-var", "some value");
      En navegadores donde el soporte para CSS @property aún no está activado, se puede usar como polyfill.
  • En la versión más reciente de Chrome para Android, curiosamente las demos anteriores a mod() funcionan incluso sin Experimental feature flags, pero mod() y sin() no hacen nada.

  • Quería esto desde hace tiempo porque quería crear algo como box-breathing.
    Quería poder ajustar todas las fases —inhalar, sostener, exhalar, sostener— al cuerpo y al efecto deseado.
    Sin algo como el enfoque de esta página, no encontraba forma de implementarlo sin JS, o sin una complejidad absurda.
    ¡Vaya, hojas de estilo! Las demos de esta página son realmente creativas.

    • ¿Eso no es simplemente una curva de animación en CSS?
  • El artículo está muy bien escrito.
    Chrome todavía no ha lanzado oficialmente el soporte para CSS mod(), así que si no usas una versión preview de Chrome, la mayoría de los ejemplos de la página no se animan.
    Si no necesitas control por ticks y solo quieres animaciones CSS fluidas en todos los navegadores, la técnica FLIP es útil.
    https://medium.com/outsystems-experts/flip-your-60-fps-anima...

  • La animación al final de la página es muy impresionante.
    Se ve más como un video que como elementos individuales.

  • https://RTCode.io es un playground web en tiempo real que funciona sin recargar ni reiniciar el estado, así que puede ser de gran ayuda para quienes trabajan con animaciones o transiciones CSS.
    La demo está en https://RTCode.io.
    Para importar un Pen, basta con cambiar el hostname de la URL a xcodepen.io.

    • ¿Alguien ha probado RTCode? Se ve interesante, pero al mirar la página principal me confunde un poco si es una alternativa a CodePen con soporte en tiempo real, o si también se puede usar en producción.