Animación CSS avanzada usando cubic-bezier()

Cuando se trata de animaciones CSS complejas, existe una tendencia a crear animaciones expansivas @keyframescon muchas declaraciones. Sin embargo, hay un par de trucos de los que quiero hablar que podrían ayudar a hacer las cosas más fáciles, sin dejar de usar CSS básico:

  1. Múltiples animaciones
  2. Funciones de temporización

El primero es más utilizado y familiar, pero el segundo es menos común. Podría haber buenas razones para ello: encadenar animaciones con comas es relativamente más fácil que asimilar las diversas funciones de sincronización que tenemos disponibles y lo que hacen. Hay una función de sincronización especialmente interesante que nos brinda control total para crear funciones de sincronización personalizadas. Eso sería cubic-bezier()y en esta publicación te mostraré su poder y cómo se puede usar para crear animaciones sofisticadas sin demasiada complejidad.

Comenzamos con un ejemplo básico que muestra cómo podemos mover una bola en direcciones interesantes, como una forma de infinito (∞):

Como puedes ver, no hay ningún código complejo: sólo dos fotogramas clave y una cubic-bezier()función. Y, sin embargo, lo que obtenemos es una animación final en forma de infinito de aspecto bastante complejo.

¿Guay, verdad? ¡Profundicemos en esto!

La cubic-bezier()funcion

Comenzamos con la definición oficial:

Una función de flexibilización de Bézier cúbica es un tipo de función de flexibilización definida por cuatro números reales que especifican los dos puntos de control, P1 y P2, de una curva de Bézier cúbica cuyos puntos finales P0 y P3 están fijos en (0, 0 ) y (1, 1) respectivamente. Las coordenadas x de P1 y P2 están restringidas al rango [0, 1].

La curva anterior define cómo se comportará la salida (eje y) en función del tiempo (eje x). Cada eje tiene un rango de [0, 1](o [0% 100%]). Si tenemos una animación que dura dos segundos ( 2s), entonces:

0 (0%) = 0s 1 (100%) = 2s

Si queremos animar leftdesde 5pxhasta 20px, entonces:

0 (0%) = 5px 1 (100%) = 20px

X, el tiempo, siempre está restringido a [0 1]; Sin embargo Y, el resultado puede ir más allá [0 1].

Mi objetivo es ajustar P1 y P2 para crear las siguientes curvas:

Puede pensar que esto es imposible de lograr porque, como se indica en la definición, P0 y P3 están fijos (0,0)y, (1,1)por lo tanto, no pueden estar en el mismo eje. Eso es cierto y usaremos algunos trucos matemáticos para “aproximarlos”.

curva parabólica

Comenzamos con la siguiente definición: cubic-bezier(0,1.5,1,1.5). Eso nos da la siguiente curva:

Nuestro objetivo es avanzar (1,1)y lograr (0,1)lo que técnicamente no es posible. Así que intentaremos fingirlo.

Anteriormente dijimos que nuestro rango es [0 1](o [0% 100%]), así que imaginemos el caso en el que 0%está muy cerca de 100%. Si, por ejemplo, queremos animar topdesde 20px (0%)hasta 20.1px (100%)entonces podemos decir que tanto el estado inicial como el final son iguales.

Hm, pero nuestro elemento no se moverá en absoluto, ¿verdad?

Bueno, se moverá un poco porque el valor de Y excede 20.1px( 100%). Pero eso no es suficiente para darnos un movimiento perceptible:

Actualicemos la curva y usémosla cubic-bezier(0,4,1,4)en su lugar. Observe cómo nuestra curva es mucho más alta que antes:

Pero todavía no hay movimiento, incluso si el valor superior se cruza 3(o 300%). Intentemos cubic-bezier(0,20,1,20):

¡Sí! empezó a moverse un poco. ¿Notaste la evolución de la curva cada vez que aumentamos el valor? Se trata de acercar nuestro punto (1,1)"visualmente" al (0,1)momento en que nos alejamos para ver la curva completa y este es el truco.

Al usar cubic-bezier(0,V,1,V)donde Vhay un valor muy grande y los estados inicial y final están muy juntos (o casi iguales), podemos simular la curva parabólica.

Un ejemplo vale más que mil palabras:

Apliqué la función "mágica" de Bézier cúbico a la topanimación, además de una lineal aplicada a left. Esto nos da la curva que queremos.

Profundizando en las matemáticas

Para aquellos de ustedes con mentalidad matemática, podemos desglosar aún más esa explicación. Un Bézier cúbico se puede definir mediante la siguiente fórmula:

P = (1−t)³P0 + 3(1−t)²tP1 + 3(1−t)t²P2 + t³P3

Cada punto se define de la siguiente manera: P0 = (0,0), P1 = (0,V), P2 = (1,V)y P3 = (1,1).

Esto nos da las dos funciones para las coordenadas xey:

  • X(t) = 3(1−t)t² + t³ = 3t² - 2t³
  • Y(t) = 3(1−t)²tV +3(1−t)t²V + t³ = t³ - 3Vt² + 3Vt

Ves nuestro gran valor y testá dentro del rango [0 1]. Si consideramos nuestro ejemplo anterior, Y(t)nos dará el valor de topwhile X(t)que es el avance del tiempo. Los puntos (X(t),Y(t))definirán entonces nuestra curva.

Encontremos el valor máximo de Y(t). Para esto necesitamos encontrar el valor de tque nos dará Y'(t) = 0(cuando la derivada es igual a 0):

Y'(t) = 3t² - 6Vt + 3V

Y'(t) = 0es una ecuación cuadrática. Me saltaré la parte aburrida y les daré el resultado, que es t = V - sqrt(V² - V).

Cuando Vsea un valor grande, tserá igual a 0.5. Entonces, Y(0.5) = Maxy X(0.5)será igual a 0.5. Eso significa que alcanzamos el valor máximo en el punto medio de la animación, que se ajusta a la curva parabólica que queremos.

Además, Y(0.5)nos dará (1 + 6V)/8y esto nos permitirá encontrar el valor máximo según V. Y como siempre usaremos un valor grande para V, podemos simplificar a 6V/8 = 0.75V.

Usamos V = 500en el último ejemplo, por lo que el valor máximo sería 375(o 37500%) y obtendríamos lo siguiente:

  • Estado inicial ( 0):top: 200px
  • Estado final ( 1):top: 199.5px

Hay una diferencia -0.5pxentre 0 y 1. Llamémoslo incremento. Para 375(o 37500%) tenemos una ecuación de 375*-0.5px = -187.5px. Nuestro elemento animado llega a top: 12.5px( 200px - 187.5px) y nos da la siguiente animación:

top: 200px (at 0% of the time ) → top: 12.5px (at 50% of the time) → top: 199.5px (at 100% of the time) 

O, expresado de otra manera:

top: 200px (at 0%) → top: 12.5px (at 50%) → top: 200px (at 100%)

Hagamos la lógica opuesta. ¿Qué valor de Vdebemos usar para que nuestro elemento alcance top: 0px? La animación será 200px → 0px → 199.5pxasí que tenemos que -200pxllegar 0px. Nuestro incremento siempre es igual a -0.5px. El valor máximo será igual a 200/0.5 = 400, lo 0.75V = 400que significa V = 533.33.

¡Nuestro elemento está tocando la cima!

Aquí hay una figura que resume los cálculos que acabamos de hacer:

Curva sinusoidal

Usaremos casi exactamente el mismo truco para crear una curva sinusoidal pero con una fórmula diferente. Esta vez usaremoscubic-bezier(0.5,V,0.5,-V)

Como hicimos antes, veamos cómo evolucionará la curva cuando aumentemos el valor:

Creo que probablemente ya te hagas una idea. Usar un valor grande para Vnos acerca a una curva sinusoidal.

Aquí hay otro con una animación continua: ¡una animación sinusoidal real!

Las matemáticas

¡Entremos en los cálculos para este! Siguiendo la misma fórmula que antes, obtendremos las siguientes funciones:

  • X(t) = 3/2(1−t)²t + 3/2(1−t)t² + t³ = (3/2)t - (3/2)t² + t³
  • Y(t) = 3(1−t)²tV - 3(1−t)t²V + t³ = (6V + 1)t³ - 9Vt² + 3Vt

Esta vez necesitamos encontrar los valores mínimo y máximo para Y(t). Y'(t) = 0nos dará dos soluciones. Después de resolver esto:

Y'(t) = 3(6V + 1)t² - 18Vt + 3V = 0

…obtenemos:

  • t' = (3V + sqrt(3V² - V))/(6V + 1)
  • t''= (3V - sqrt(3V² - V))/(6V + 1)

Para un valor grande de V, tenemos t'=0.211y t"=0.789. Eso significa que Y(0.211) = Maxy Y(0.789) = Min. Eso también significa que X(0.211)= 0.26y X(0.789) = 0.74. En otras palabras, alcanzamos el Max el 26% de las veces y el Min el 74% de las veces.

Y(0.211)es igual a 0.289Vy Y(0.789)a -0.289V. Obtuvimos esos valores con algo de redondeo considerando que Ves muy grande.

Nuestra curva sinusoidal también debería cruzar el eje x (o Y(t) = 0) en la mitad del tiempo (o X(t) = 0.5). Para demostrar esto, utilizamos la segunda derivada de Y(t)— que debería ser igual a 0— so Y''(t) = 0.

Y''(t) = 6(6V + 1)t - 18V = 0

La solución es 3V/(6V + 1), y para un Vvalor grande, la solución es 0.5. Que nos da Y(0.5) = 0y X(0.5) = 0.5que confirma que nuestra curva cruza el (0.5,0)punto.

Ahora consideremos el ejemplo anterior e intentemos encontrar el valor de Vque nos lleva de regreso a top: 0%. Tenemos:

  • Estado inicial ( 0):top: 50%
  • Estado final ( 1):top: 49.9%
  • Incremento:-0.1%

Necesitamos -50%llegar top: 0%, entonces 0.289V*-0.1% = -50%lo que nos da V = 1730.10.

Como puedes ver, nuestro elemento toca la parte superior y desaparece en la parte inferior porque tenemos la siguiente animación:

top: 50% → top: 0% → top: 50% → top: 100% → top: 50% → and so on ... 

Una cifra para resumir el cálculo:

Y un ejemplo para ilustrar todas las curvas juntas:

¡Sí, ves cuatro curvas! Si miras de cerca, notarás que estoy usando dos animaciones diferentes, una que va a 49.9%(un incremento de -0.01%) y otra que va a 50.1%(un incremento de +0.01%). Al cambiar el signo del incremento, controlamos la dirección de la curva. También podemos controlar los otros parámetros del bézier cúbico (no el Vque debería seguir siendo un valor grande) para crear más variaciones a partir de las mismas curvas.

Y a continuación, una demostración interactiva:

Volviendo a nuestro ejemplo

Volvamos a nuestro ejemplo inicial de una bola que se mueve en forma de símbolo de infinito. Simplemente combiné dos animaciones sinusoidales para que funcionara.

Si combinamos lo que hicimos anteriormente con el concepto de múltiples animaciones, podemos obtener resultados sorprendentes. Aquí nuevamente está el ejemplo inicial, esta vez como una demostración interactiva. Cambie los valores y vea la magia:

Vayamos más allá y agreguemos un poco de CSS Houdini a la mezcla. Podemos animar una declaración de transformación compleja gracias a @property(pero CSS Houdini está limitado a la compatibilidad con Chrome y Edge por el momento).

¿Qué tipo de dibujos puedes hacer con eso? Aquí hay algunos que pude hacer:

Y aquí hay una animación del espirógrafo:

Y una versión sin CSS Houdini:

Hay algunas cosas que podemos aprender de estos ejemplos:

  • Cada fotograma clave se define utilizando solo una declaración que contiene el incremento.
  • La posición del elemento y la animación son independientes. Podemos colocar fácilmente el elemento en cualquier lugar sin necesidad de ajustar la animación.
  • No hicimos ningún cálculo. No hay muchos ángulos o valores de píxeles. Sólo necesitamos un valor pequeño dentro del fotograma clave y un valor grande dentro de la cubic-bezier()función.
  • Toda la animación se puede controlar simplemente ajustando el valor de duración.

¿Qué pasa con la transición?

La misma técnica también se puede utilizar con la transitionpropiedad CSS, ya que sigue la misma lógica cuando se trata de funciones de temporización. Esto es genial porque podemos evitar fotogramas clave al crear algún efecto de desplazamiento complejo.

Esto es lo que hice sin fotogramas clave:

Mario salta gracias a la curva parabólica. No necesitábamos ningún fotograma clave para crear esa animación de vibración al pasar el mouse. La curva sinusoidal es perfectamente capaz de realizar todo el trabajo.

Aquí hay otra versión de Mario, esta vez usando CSS Houdini. Y sí, sigue saltando gracias a la curva parabólica:

Por si acaso, aquí hay efectos de desplazamiento más sofisticados sin fotogramas clave (nuevamente, solo Chrome y Edge):

¡Eso es todo!

Ahora tienes algunas cubic-bezier()curvas mágicas y las matemáticas detrás de ellas. La ventaja, por supuesto, es que funciones de sincronización personalizadas como esta nos permiten realizar animaciones sofisticadas sin los fotogramas clave complejos que generalmente utilizamos.

Entiendo que no todo el mundo tiene mentalidad matemática y eso está bien. Hay herramientas que pueden ayudar, como Ceaser de Matthew Lein, que le permite arrastrar los puntos de la curva para obtener lo que necesita. Y, si aún no lo tiene marcado como favorito, cubic-bezier.com es otro. Si quieres jugar con cúbico-bezier fuera del mundo CSS, te recomiendo desmos donde puedes ver algunas fórmulas matemáticas.

Independientemente de cómo obtenga sus cubic-bezier()valores, es de esperar que ahora tenga una idea de sus poderes y de cómo pueden ayudar a crear un código mejor en el proceso.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir