La animación escalonada, también conocida como “seguimiento” o “acción superpuesta” es uno de los doce principios de animación de Disney definidos por Ollie Johnston y Frank Thomas en su libro de 1981 “La ilusión de la vida”. En esencia, el concepto se ocupa de animar objetos en sucesión retrasada para producir un movimiento fluido.
Sin embargo, la técnica no solo se aplica a las lindas animaciones de personajes. El aspecto de diseño de movimiento de una interfaz digital tiene implicaciones significativas en UX, la percepción del usuario y la “sensación”. Google incluso menciona la animación escalonada en su página Motion Choreography, como parte de la guía Material Design:
Si bien el tema del diseño de movimiento es realmente amplio, a menudo me encuentro aplicando fragmentos incluso en los proyectos más pequeños. Durante el proceso de diseño del anuncio interactivo de Coca-Cola en Eko, me encargaron crear una animación para mostrarla mientras se carga el video interactivo, y así nació esta maqueta:
A primera vista, esta animación parece trivial de implementar en CSS, ¡pero resulta que no es así! Si bien puede ser más simple con GSAP y la nueva API de animaciones web, hacerlo con CSS requiere algunos trucos que explicaré en esta publicación. ¿Por qué usar CSS entonces? En este caso, como la animación estaba destinada a ejecutarse mientras el usuario espera a que se carguen los activos, no tenía mucho sentido cargar una biblioteca de animación solo para mostrar una rueda de carga.
Primero, un poco sobre la anatomía de la animación.
Hay cuatro círculos, posicionados absolutamente dentro de un contenedor con desbordamiento: ocultos para enmarcar y recortar los bordes de los dos círculos más exteriores. ¿Por qué cuatro y no tres? Porque el primero está fuera de la pantalla, esperando entrar al escenario a la izquierda y el último existe en el marco del escenario a la derecha. Los otros dos están siempre en el marco. De esta forma, el estado final de la iteración de la animación se ve exactamente como su estado inicial. El círculo 1 ocupa el lugar del círculo 2, el círculo 2 ocupa el lugar del círculo 3 y así sucesivamente.
Aquí está el HTML básico:
<div id="container">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
Y el CSS que lo acompaña:
#container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 160px;
height: 40px;
display: block;
overflow: hidden;
}
span {
width: 40px;
height: 40px;
border-radius: 50%;
background: #4df5c4;
display: inline-block;
position: absolute;
transform: translateX(0px);
}
Probemos esto con una animación simple para cada círculo que traduce X de 0 a 60 píxeles:
Vea el cargador de puntos Pen: sin escalonamientos de Opher Vishnia (@OpherV) en CodePen.
Parece un poco raro y robótico, ¿verdad? Eso es porque nos falta un componente importante: la animación escalonada. Es decir, la animación de cada círculo debe comenzar un poco después de su predecesor. “¡No hay problema!”, podrías pensar para ti mismo, “utilicemos la propiedad animation-delay”. “Le daremos al 4º círculo un valor de 0s, al 3º de 0,15s y así sucesivamente”. Muy bien, probemos eso:
Vea el cargador de puntos Pen, roto por Opher Vishnia (@OpherV) en CodePen.
Hmm… ¿Qué acaba de pasar? La propiedad animation-delay
afecta solo el retraso inicial antes de que comiencen las animaciones. No agrega demoras adicionales entre cada iteración, por lo que la animación se desincroniza como en el siguiente diagrama:
Matemáticas al rescate
Para superar esto, integré el retraso en la animación. Las animaciones de fotogramas clave de CSS se especifican en porcentajes y, con algunos cálculos, puede usarlos para definir cuánto retraso debe incluir la animación. Por ejemplo, si establece un animation-duration
de 1 s y especifique su fotograma clave de inicio en 0 %, los mismos valores en 20 %, su final en 80 % y los mismos valores finales en 100 %, su animación esperará 0,2 segundos, se ejecutará durante 0,6 segundos y luego esperará otros 0,2 segundos.
En mi caso, quería que cada círculo esperara con un tiempo de escalonamiento de 0,15 segundos antes de realizar la animación real en 0,5 segundos, con todo el proceso en 1 segundo. Esto significa que la animación del cuarto círculo espera 0 segundos, luego se anima durante 0,5 segundos y espera otros 0,5 segundos. El segundo círculo espera 0,15 segundos, luego anima 0,5 segundos y espera 0,35 segundos y así sucesivamente.
Para lograr esto, necesita cuatro fotogramas clave (o tres pares de fotogramas clave): 1 y 2 representan la espera escalonada, 2 y 3 representan el tiempo de animación real, mientras que 3 y 4 representan la espera final. El “truco” es entender cómo convertir los tiempos requeridos en porcentajes de fotogramas clave, pero ese es un cálculo relativamente simple. Por ejemplo, el segundo círculo debe esperar 0,15 * 2 = 0,3 segundos y luego animarse durante 0,5 segundos. Sé que el tiempo total de la animación es de un segundo, por lo que los porcentajes de fotogramas clave se calculan así:
0s = 0%
0.3s = 0.3 / 1s * 100 = 30%
0.8s = (0.3 + 0.5) / 1s * 100 = 80%
1s = 100%
El resultado final se parece a esto:
Con toda la animación, incluido el tiempo de escalonamiento y la espera integrados en los fotogramas clave de CSS que tardan exactamente un segundo, la animación no se desincroniza.
Afortunadamente, Sass nos permite automatizar este proceso con un bucle for simple y algunas matemáticas en línea, que finalmente se compilan en una serie de animaciones de fotogramas clave. De esta manera, puede manipular las variables de tiempo para experimentar y probar lo que funcione mejor para su animación:
@mixin createCircleAnimation($i, $animTime, $totalTime, $delay) {
@include keyframes(circle#{$i}) {
0% {
@include transform(translateX(0));
}
#{($i * $delay)/$totalTime * 100}% {
@include transform(translateX(0));
}
#{($i * $delay + $animTime)/$totalTime * 100}% {
@include transform(translateX(60px));
}
100% {
@include transform(translateX(60px));
}
}
}
$animTime: 0.5s;
$totalTime: 1s;
$staggerTime: 0.15s;
@for $i from 0 through 3 {
@include createCircleAnimation($i, $animTime, $totalTime, $staggerTime);
span:nth-child(#{($i + 1)}) {
animation: circle#{(3 - $i)} $totalTime infinite;
left: #{$i * 60 - 60 }px;
}
}
Y listo, aquí está el resultado final.
<
p data-height=”450 data-theme-id=” 1″=”” data-slug-hash=”bEydYo” data-default-tab=”result” data-user=”OpherV” data-embed-version= ”2″ data-pen-title=”animación de carga de puntos – SASS stagger” class=”codepen”>Vea la animación de carga de puntos Pen – SASS stagger de Opher Vishnia (@OpherV) en CodePen.
Hay dos advertencias principales con este método:
Primero, debe asegurarse de que el tiempo de escalonamiento/tiempo de animación definido no sea demasiado largo como para superponerse al tiempo total de animación; de lo contrario, las matemáticas (y la animación) se romperán.
En segundo lugar, este método genera una gran cantidad de código CSS, especialmente si está utilizando Sass para emitir todos los prefijos para la compatibilidad del navegador. En mi ejemplo, solo tenía cuatro elementos para animar, pero si el suyo tiene más elementos, la cantidad de código generado podría no valer la pena y probablemente desee seguir con las bibliotecas de animación basadas en JS, como GSAP. Aún así, hacer esto completamente en CSS es genial.
Hacer la vida más fácil
Para contrastar la verbosidad de la solución Sass, me gustaría mostrarle cómo se puede lograr lo mismo fácilmente con el uso de la línea de tiempo de GSAP, y staggerTo
función:
Vea la animación de carga de puntos del bolígrafo: GSAP de Opher Vishnia (@OpherV) en CodePen.
Hay dos partes interesantes aquí. Primero, el último parámetro de staggerTo
que define el tiempo de espera entre los elementos animados, se establece en un valor negativo (-0,15). Esto permite que los elementos se escalonen en orden inverso (circule 4–3–2–1 en lugar de 1–2–3–4). Genial, ¿eh?
En segundo lugar, vea el bit con tl.set({}, {}, "1");
? ¿De qué se trata esta extraña sintaxis? Ese es un buen truco para implementar el tiempo de espera al final de la animación de cada círculo. Esencialmente, al establecer un objeto vacío en un objeto vacío en el momento 1, la animación de la línea de tiempo ahora se repetirá después de la marca de 1 segundo, en lugar de después de que la animación del círculo haya terminado.
Mirando hacia el futuro
La API de animaciones web es el chico nuevo y emocionante del bloque, pero está fuera del alcance de este artículo. Sin embargo, no pude resistirme a brindarle una implementación de muestra, que utiliza las mismas matemáticas que la implementación de CSS:
Vea la animación de carga de puntos del lápiz: WAAPI de Opher Vishnia (@OpherV) en CodePen.
¿Fue útil? ¿Ha creado algunas animaciones suaves usando esta técnica? Hágamelo saber!