Técnicas de animación CSS basadas en tiempo
(yuanchuan.dev)- 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
--ten lugar dekeyframes - Al combinar
@property --tcon una animación raíz de duración86400000ms, el tiempo aumenta en milisegundos y puede vincularse al cálculo de propiedades comorotateytranslate - 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()yround()se puede limitar, repetir, hacer oscilar y suavizar el valor del tiempo para expresar easing personalizado y movimiento de posición sinkeyframes css-doodleofrece expresiones abreviadas como@t,@Ty@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
--tpara seguir el tiempo en milisegundos - El ejemplo básico declara
@property --tcomo<integer>, la cambia de0a86400000en@keyframes tick, y aplica en:rootuna animación86400000ms linear infinite --taumenta en1cada milisegundo y llega a1000en 1 segundo- Con
counter()se puede mostrar en pantalla el valor actual de--t - Si se conecta un cálculo basado en
--ta una propiedad, comorotate: 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)))
- 8fps:
Convertir un valor de tiempo creciente en un valor de animación
- El valor
--tsigue aumentando en una sola dirección - Un valor
anglepuede superar360degsin 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
--ten 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
--tse pueden crear easing personalizados que podrían ser difíciles de lograr concubic-bezier() - Primero se limita
--ta un valor entre0y1--t01: calc(min(1000, var(--t)) / 1000)cambia de 0 a 1 en 1 segundo
-
ease-out-cubic
ease-out-cubicse compone de la fórmula1 - pow(1 - t, 3)- Después de crear el valor
--ease-out-cubic, se conecta atranslate: calc(var(--ease-out-cubic) * 270px)
-
ease-out-elastic
ease-out-elasticse compone de la fórmulapow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1- Del mismo modo, se crea el valor
--ease-out-elasticy se multiplica entranslatepara aplicarlo a un movimiento de 270px
Reducir fórmulas con css-doodle
- A medida que las fórmulas se vuelven más complejas,
var()ycalc()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
rotatese acorta así- Antes:
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) css-doodle:rotate: @t(/1000, %10, *5deg)
- Antes:
- 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)
- Además de
-
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 usarotate: 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
keyframespuede 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
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: -1500msinicia 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...
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?
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.
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”.
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.
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
@propertypersonalizado 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-valuede respaldo.Chrome usa
initial-valuecuando una variable CSS no está definida o está establecida con un valor inválido.Firefox usa
initial-valuesolo 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
about:configy activalayout.css.properties-and-values.enabled.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
@propertyaú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, peromod()ysin()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.
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.