Animación CSS avanzada usando cubic-bezier () | Programar Plus

Cuando se trata de animaciones CSS complejas, existe una tendencia a crear expansivas @keyframes con muchas declaraciones. Sin embargo, hay un par de trucos de los que quiero hablar que podrían ayudar a facilitar las cosas, mientras permanecen en CSS vainilla:

  1. Varias animaciones
  2. Funciones de cronometraje

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 están disponibles para nosotros y lo que hacen. Hay una función de sincronización especialmente ordenada que nos da un 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 elegantes sin demasiada complejidad.

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

Como puede ver, no hay un código complejo, solo dos fotogramas clave y un cubic-bezier() función. Y, sin embargo, lo que obtenemos es una animación final de forma infinita de aspecto bastante complejo.

¿Guay, verdad? ¡Vamos a profundizar en esto!

El cubic-bezier() función

Comencemos con la definición oficial:

Una función de aceleración de Bézier cúbica es un tipo de función de aceleració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 extremos 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 left desde 5px a 20px, entonces:

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

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

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

Curva parabólica

Curva sinusoidal

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

Curva parabólica

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

cubic-bezier(0,1.5,1,1.5)

Nuestro objetivo es movernos (1,1) y hacerlo en (0,1) que no es técnicamente posible. Así que intentaremos fingirlo.

Anteriormente dijimos que nuestra gama es [0 1] (o [0% 100%]) así que imaginemos el caso cuando 0% está muy cerca de 100%. Si, por ejemplo, queremos animar top desde 20px (0%) a 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 usemos cubic-bezier(0,4,1,4) en lugar de. Observe cómo nuestra curva es mucho más alta que antes:

cúbico-bezier (0,4,1,4)

Pero aún así, todavía no hay movimiento, incluso si el valor superior está cruzando 3 (o 300%). Intentemos cubic-bezier(0,20,1,20):

cúbico-bezier (0,20,1,20)

¡Sí! empezó a moverse un poco. ¿Notaste la evolución de la curva cada vez que aumentamos el valor? Está haciendo nuestro punto (1,1) “Visualmente” más cerca de (0,1) cuando nos alejamos para ver la curva completa y este es el truco.

Mediante el uso cubic-bezier(0,V,1,V) donde V es un valor muy grande y tanto el estado inicial como el 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” cúbico-bezier allí a la top animación, más una lineal aplicada a left. Esto nos da la curva que queremos.

Profundizando en las matemáticas

Para aquellos de ustedes que tienen mentalidad matemática, podemos desglosar esa explicación aún más. 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

    V es nuestro gran valor y t está dentro del rango [0 1]. Si consideramos nuestro ejemplo anterior, Y

    Encontremos el valor máximo de Y

    Y'
    

    Y'

    Cuándo V es un gran valor, t será igual a 0.5. Entonces, Y(0.5) = Max y 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.

    También, Y(0.5) nos dará (1 + 6V)/8 y esto nos permitirá encontrar el valor máximo basado en V. Y dado que siempre usaremos un gran valor para V, podemos simplificar a 6V/8 = 0.75V.

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

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

    Hay una diferencia de -0.5px entre 0 y 1. Llamémoslo incremento. Para 375 (o 37500%) tenemos una ecuación de 375*-0.5px = -187.5px. Nuestro elemento animado está llegando 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 V deberíamos usar para hacer que nuestro elemento alcance top: 0px? La animación será 200px → 0px → 199.5px, así que necesitamos -200px alcanzar 0px. Nuestro incremento es siempre igual a -0.5px. El valor máximo será igual a 200/0.5 = 400, entonces 0.75V = 400 lo que significa V = 533.33.

    ¡Nuestro elemento está tocando la cima!

    Aquí hay una cifra que resume las matemáticas que acabamos de hacer:

    Curva parabólica usando cubic-bezier (0, V, 1, V)

    Curva sinusoidal

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

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

    Tres gráficos de izquierda a derecha, que muestran cómo la curva sinusoidal se estrecha a medida que aumenta el valor de V.

    Creo que probablemente ya tengas la idea. Usando un gran valor para V nos acerca a una curva sinusoidal.

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

    Las matemáticas

    ¡Vamos a hacer las matemáticas para este! Siguiendo la misma fórmula que antes, obtendremos las siguientes funciones:

    • X

      Esta vez necesitamos encontrar los valores mínimo y máximo para Y

      Y'
      

      …obtenemos:

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

      Por un gran valor de V, tenemos t'=0.211 y t"=0.789. Eso significa que Y(0.211) = Max y Y(0.789) = Min. Eso también significa que X(0.211)= 0.26 y X(0.789) = 0.74. En otras palabras, alcanzamos el máximo en el 26% del tiempo y el mínimo en el 74% del tiempo.

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

      Nuestra curva sinusoidal también debe cruzar el eje x (o Y

      Y''
      

      La solucion es 3V/(6V + 1)y por un gran V valor, la solución es 0.5. Que nos dan Y(0.5) = 0 y X(0.5) = 0.5 lo que confirma que nuestra curva cruza el (0.5,0) punto.

      Ahora consideremos el ejemplo anterior e intentemos encontrar el valor de V eso nos lleva de vuelta a top: 0%. Tenemos:

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

      Nosotros necesitamos -50% alcanzar top: 0%, entonces 0.289V*-0.1% = -50% que nos da V = 1730.10.

      Como puede ver, nuestro elemento está tocando la parte superior y desapareciendo 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:

      Curva sinusoidal usando cubic-bezier (0.5, V, 0.5, -V)

      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 para 49.9% (un incremento de -0.01%) y otro 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 bezier cúbico (no el V uno que debería seguir siendo un gran valor) 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 asombrosos. 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 al soporte de Chrome y Edge en este momento).

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

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

      Y una versión sin CSS Houdini:

      Hay algunas cosas que se pueden extraer 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 cálculos. No hay muchos ángulos o valores de píxeles. Solo necesitamos un valor mínimo dentro del fotograma clave y un valor grande dentro del 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 CSS transition propiedad ya que sigue la misma lógica cuando se trata de funciones de temporización. Esto es genial porque podemos evitar los fotogramas clave al crear un efecto de desplazamiento complejo.

      Esto es lo que hice sin fotogramas clave:

      Mario está saltando gracias a la curva parabólica. No necesitábamos fotogramas clave para crear esa animación de movimiento al pasar el mouse. La curva sinusoidal es perfectamente capaz de hacer todo el trabajo.

      Aquí hay otra versión de Mario, esta vez usando CSS Houdini. Y, sí, todavía está 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!

      Ahora tienes algo de magia cubic-bezier() curvas y las matemáticas detrás de ellas. El beneficio, por supuesto, es que las funciones de sincronización personalizadas como esta nos permiten hacer animaciones sofisticadas sin los complejos fotogramas clave que generalmente buscamos.

      Entiendo que no todo el mundo tiene una mentalidad matemática y eso está bien. Hay herramientas para 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 ha marcado, cubic-bezier.com es otro. Si quieres jugar con cubic-bezier fuera del mundo CSS, te recomiendo desmos donde puedes ver algunas fórmulas matemáticas.

      Independientemente de cómo obtenga su 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 más agradable en el proceso.

      (Visited 4 times, 1 visits today)