Hackear el estado de animación CSS y el tiempo de reproducción | consejos CSS

Wolfenstein solo en CSS es un pequeño proyecto que hice hace unas semanas. Fue un experimento con transformaciones y animaciones 3D CSS.

Inspirándome en la demostración de FPS y otro CodePen de Wolfenstein, decidí hacer mi propia versión. Se basa libremente en el Episodio 1 - Piso 9 del juego Wolfenstein 3D original.

Editor: este juego requiere intencionalmente una reacción rápida para evitar una pantalla Game Over.

Aquí hay un video de reproducción:

En pocas palabras, mi proyecto no es más que una animación CSS larga y cuidadosamente escrita. Además de algunos casos de piratería de casillas de verificación.

:checked ~ div { animation-name: spin; }

El entorno consta de caras de cuadrícula 3D y las animaciones son en su mayoría simples traslaciones y rotaciones 3D. Nada realmente lujoso.

Sin embargo, dos problemas fueron particularmente difíciles de resolver:

  • Reproduzca la animación de "disparo de arma" cada vez que el jugador haga clic en un enemigo.
  • Cuando el jefe rápido haya recibido el último golpe, entra en una cámara lenta espectacular.

Técnicamente, esto significaba:

  • Reproduzca una animación cuando la siguiente casilla esté marcada.
  • Reduzca la velocidad de una animación, cuando una casilla está marcada.

De hecho, ¡ninguno de los dos se resolvió correctamente en mi proyecto! O terminé usando soluciones alternativas o simplemente me di por vencido.

Por otro lado, después de algunas investigaciones, finalmente encontré la clave para ambos problemas: cambiar las propiedades de ejecutar animaciones CSS. En este artículo profundizaremos en este tema:

  • Muchos ejemplos interactivos.
  • Disecciones: ¿Cómo funciona (o no funciona) cada ejemplo?
  • Detrás de escena: ¿Cómo manejan los navegadores los estados de animación?

Déjame "tirar mis ladrillos".

Problema 1: reproducir la animación

El primer ejemplo: "solo otra casilla de verificación"

Mi primera corazonada fue "simplemente agregue otra casilla de verificación", que no funciona:

Cada casilla de verificación funciona individualmente, pero no ambas juntas. Si una casilla ya está marcada, la otra ya no funciona.

Así es como funciona (o "no funciona"):

  1. los animation-name de <div> es none por defecto.
  2. El usuario hace clic en una casilla de verificación, animation-name se convierte en spiny la animación comienza de nuevo desde el principio.
  3. Después de un tiempo, el usuario hace clic en la otra casilla de verificación. Entra en vigor una nueva regla CSS, pero animation-name es todavía spin, lo que significa que no se agrega ni elimina ninguna animación. La animación sigue reproduciéndose como si nada hubiera pasado.

El segundo ejemplo: "clonar la animación"

Un enfoque de trabajo es clonar la animación:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }

Así es como funciona:

  1. animation-name es none inicialmente.
  2. El usuario hace clic en "¡Girar!", animation-name se convierte en spin1. Animación spin1 se inicia desde el principio porque se acaba de agregar.
  3. El usuario hace clic en "¡Girar de nuevo!", animation-name se convierte en spin2. Animación spin2 se inicia desde el principio porque se acaba de agregar.

Tenga en cuenta que en el paso 3, spin1 se elimina debido al orden de las reglas de CSS. No funcionará si "¡Gira de nuevo!" se comprueba primero.

El tercer ejemplo: "añadir la misma animación"

Otro enfoque de trabajo es "agregar la misma animación":

#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }

Esto es similar al ejemplo anterior. De hecho, puedes entender el comportamiento de esta manera:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }

Tenga en cuenta que cuando "¡Girar de nuevo!" está marcada, la antigua animación actual se convierte en la segunda animación en la nueva lista, que puede no ser intuitiva. Una consecuencia directa es: el truco no funcionará si animation-fill-mode es forwards. Aquí hay una demostración:

Si te preguntas por qué es así, aquí tienes algunas pistas:

  • animation-fill-mode es none predeterminado, lo que significa que "La animación no tiene efecto si no se está reproduciendo".
  • animation-fill-mode: forwards; significa "Después de que la animación termine de reproducirse, debe permanecer en el último fotograma clave para siempre".
  • spin1la decision siempre gana spin2es porque spin1 aparece más adelante en la lista.
  • Supongamos que el usuario hace clic en "¡Girar!", espera un giro completo y luego hace clic en "¡Girar de nuevo!". En ese momento. spin1 ya esta terminado y spin2 solo empieza.

Discusión

Regla básica: no puede "reiniciar" una animación CSS existente. En su lugar, desea agregar y reproducir una nueva animación. Esto puede ser confirmado por la especificación W3C:

Una vez que ha comenzado una animación, continúa hasta que finaliza o se elimina el nombre de la animación.

Ahora, comparando los dos últimos ejemplos, creo que, en la práctica, la "clonación de animaciones" a menudo debería funcionar mejor, especialmente cuando el preprocesador CSS está disponible.

Problema 2: Cámara lenta

Puede pensar que ralentizar una animación es solo una cuestión de configurar un tiempo más largo. animation-duration:

div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }

De hecho, funciona:

…o sí?

Con algunos ajustes, debería ser más fácil ver el problema.

Sí, la animación se ralentiza. Y no, no se ve bien. El perro "salta" (casi) siempre cuando marcas la casilla. Además, el perro parece saltar a una posición aleatoria en lugar de a la posición original. ¿Cómo?

Sería más fácil de entender si introdujéramos dos "elementos de sombra":

Ambos elementos de sombra realizan las mismas animaciones con diferentes animation-duration. Y no se ven afectados por la casilla de verificación.

Cuando marca la casilla, el elemento cambia inmediatamente entre los estados de dos elementos de sombra.

Citando la especificación W3C:

Los cambios realizados en los valores de propiedad de la animación mientras se ejecuta la animación se aplican como si la animación tuviera esos valores desde el principio.

Esto sigue el diseño sin estado, que permite a los navegadores determinar fácilmente el valor animado. El cálculo real se describe aquí y aquí.

Otro intento

Una idea es pausar la animación actual y luego agregar una animación más lenta que tome el control:

div {
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

Entonces funciona:

…o sí?

Se ralentiza cuando haces clic en "¡Cámara lenta!". Pero si espera un círculo completo, verá un "salto". De hecho, siempre salta a la posición donde "¡Slowmo!" se hace clic.

La razón es que no tenemos from fotograma clave definido, y no deberíamos. Cuando el usuario hace clic en "¡Cámara lenta!", spin1 se detiene en una determinada posición, y spin2 comienza exactamente en la misma posición. Simplemente no podemos predecir esta posición por adelantado... ¿o sí?

Una solución de trabajo

¡Podemos! Usando una propiedad personalizada, podemos capturar el ángulo en la primera animación y luego pasarlo a la segunda animación:

div {
  transform: rotate(var(--angle1));
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  transform: rotate(var(--angle2));
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

@keyframes spin1 {
  to {
    --angle1: 360deg;
  }
}

@keyframes spin2 {
  from {
    --angle2: var(--angle1);
  }
  to {
    --angle2: calc(var(--angle1) + 360deg);
  }
}

Notar: @property se utiliza en este ejemplo, que no es compatible con todos los navegadores.

La solución "perfecta"

Hay una advertencia a la solución anterior: "salir de cámara lenta" no funciona bien.

Aquí hay una mejor solución:

En esta versión, se puede ingresar o salir de la cámara lenta sin problemas. Tampoco se utilizan características experimentales. Entonces, ¿es esta la solución ideal? Si y no.

Esta solución funciona como un "cambio de marcha":

  • Engranajes: hay dos <div>s. Uno es el padre del otro. Ambos tienen la spin animación pero con diferentes animation-duration. El estado final del elemento es la acumulación de las dos animaciones.
  • Cambio de marcha: Al principio, solo una <div> tiene su animación en ejecución. El otro está en pausa. Cuando la casilla está marcada, las dos animaciones intercambian sus estados.

Aunque me gusta mucho el resultado, hay un problema: es una gran proeza de spin animation, que no funciona para otros tipos de animaciones en general.

Una solución práctica (con JS)

Para animaciones generales, es posible lograr la función de cámara lenta con un poco de JavaScript:

Una explicación rápida:

  • Se utiliza una propiedad personalizada para seguir el progreso de la animación.
  • La animación se "reinicia" cuando se marca la casilla.
  • El código JS calcula el correcto animation-delay para garantizar una transición sin problemas. Recomiendo este artículo si no está familiarizado con los valores negativos de animation-delay.

Puede pensar en esta solución como un híbrido del enfoque de "reiniciar animación" y "cambiar de marcha".

Aquí es importante seguir correctamente el progreso de la animación. Las soluciones alternativas son posibles si @property no está disponible. Por ejemplo, esta versión utiliza z-index para seguir el progreso:

Nota al margen: originalmente también intenté crear una versión solo de CSS, pero no tuve éxito. Aunque no estoy 100% seguro, creo que es porque animation-delay no está animado.

Aquí hay una versión con un mínimo de JavaScript. Solo funciona "ingresar inactivo".

¡Avísame si logras crear una versión solo de CSS!

Reduzca la velocidad de cualquier animación (con JS)

Finalmente, me gustaría compartir una solución que funcione para (casi) cualquier animación, incluso con múltiples complejos. @keyframes:

Básicamente, debe agregar el seguimiento del progreso de la animación y luego calcular cuidadosamente animation-delay para la nueva animación. Sin embargo, a veces puede ser difícil (pero posible) obtener los valores correctos.

Por ejemplo:

  • animation-timing-function no es linear.
  • animation-direction no es normal.
  • varios valores en animation-name con diferente animation-duration'la arena animation-delay's.

Este método también se describe aquí para la API de animaciones web.

Gracias

Empecé por este camino después de encontrarme con proyectos de solo CSS. Algunas eran delicadas obras de arte y otras eran artilugios complejos. Mis favoritos son los que involucran objetos 3D, por ejemplo, esta pelota saltarina y este cubo de embalaje.

Al principio no tenía ni idea de cómo se hacían. Luego leí y aprendí mucho de este lindo tutorial de Ana Tudor.

Resultó que construir y animar objetos 3D con CSS no es muy diferente a hacerlo con Blender, solo que con un sabor un poco diferente.

Conclusión

En este artículo, analizamos el comportamiento de las animaciones CSS cuando un animate-* la propiedad esta alterada. En particular, hemos desarrollado soluciones para "reproducir una animación" y "animación en cámara lenta".

Espero que encuentres este artículo interesante. ¡Por favor, hágame saber sus pensamientos!

Si quieres conocer otros artículos parecidos a Hackear el estado de animación CSS y el tiempo de reproducción | consejos CSS puedes visitar la categoría Estilo.

Deja un comentario

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

Subir

Esta página web utiliza cookies para analizar de forma anónima y estadística el uso que haces de la web, mejorar los contenidos y tu experiencia de navegación. Para más información accede a la Política de Cookies . Ver mas