Muñecas Matryoshka animadas en CSS

Índice
  1. Estilo básico
  2. Los mecánicos
  3. Últimos retoques

Aquí tienes uno divertido. ¿Cómo podríamos crear un conjunto de esas geniales muñecas Matryoshka que se anidan una dentro de la otra… pero en CSS?

Jugué con esta idea en mi cabeza por un rato. Luego, vi un tweet de CSS-Tricks y la imagen del artículo tenía las muñecas. ¡Lo tomé como una señal! Llegó el momento de poner los dedos en el teclado.

Nuestro objetivo aquí es hacerlos divertidos e interactivos, donde podamos hacer clic en un muñeco para abrirlo y revelar otro muñeco más pequeño. Ah, y quédate solo con CSS para la funcionalidad. Y mientras estamos en eso, reemplacemos las muñecas con nuestro propio personaje, digamos un oso CodePen. Algo como esto:

Para empezar, no nos detendremos en hacer las cosas bonitas. Primero marquemos la página y analicemos la mecánica.

No podemos tener una cantidad infinita de muñecas. Cuando lleguemos a la muñeca más interna, sería bueno poder restablecer las muñecas sin tener que actualizar la página. Un buen truco para esto es envolver nuestra escena en formato HTML. De esa manera podemos usar una entrada y establecer el typeatributo para resetevitar el uso de JavaScript.

form  input type="reset"/  label for="reset"Reset/label/form

A continuación, necesitamos algunas muñecas. Oh osos. O algo para empezar. La clave aquí será utilizar el clásico truco de casilla de verificación y cualquier etiqueta de formulario asociada. Como nota, usaré Pug para manejar el marcado porque admite bucles, lo que facilita un poco las cosas. Pero ciertamente puedes escribir el HTML a mano. Aquí está el comienzo con los campos de formulario y las etiquetas que configuran el truco de la casilla de verificación.

Intente hacer clic en algunas de las entradas y presione la entrada Restablecer. Todos ellos quedan sin control. Bien, lo usaremos.

Tenemos cierta interactividad pero todavía no está sucediendo nada. Aquí está el plan:

  1. Solo mostraremos una casilla de verificación a la vez
  2. Marcar una casilla de verificación debería revelar la etiqueta de la siguiente casilla de verificación.
  3. Cuando lleguemos a la última casilla de verificación, nuestra única opción debería ser restablecer el formulario.

El truco será utilizar el combinador de hermanos adyacentes CSS ( +).

input:checked + label + input + label {  display: block;}

Cuando se marca una casilla de verificación, debemos mostrar la etiqueta de la siguiente muñeca, que serán tres hermanos en el DOM. ¿Cómo hacemos visible la primera etiqueta? Dale una forma clara display: blocka través de estilos en línea en nuestro marcado. Juntando esto, tenemos algo como esto:

Al hacer clic en cada etiqueta se muestra la siguiente. Espera, ¡la última etiqueta no se muestra! Eso es correcto. Y eso se debe a que la última etiqueta no tiene casilla de verificación. Necesitamos agregar una regla que se ajuste a esa última etiqueta.

input:checked + label + input + label,input:checked + label + label {  display: block;}

Fresco. Estamos llegando a alguna parte. Esa es la mecánica básica. Ahora las cosas se van a poner un poco más complicadas.

Estilo básico

Entonces quizás estés pensando: "¿Por qué no ocultamos la etiqueta marcada?" ¡Buena pregunta! Pero, si lo ocultamos inmediatamente, no tendremos ninguna transición entre el muñeco actual y el siguiente. Antes de comenzar a animar nuestras muñecas, creemos cajas básicas que representarán una muñeca. Podemos diseñarlos para que imiten el contorno de la muñeca sin detalles.

.doll {  color: #fff;  cursor: pointer;  height: 200px;  font-size: 2rem;  left: 50%;  position: absolute;  text-align: center;  top: 50%;  transform: translate(-50%, -50%);  width: 100px;}.doll:nth-of-type(even) {  background: #00f;}.doll:nth-of-type(odd) {  background: #f00;}

Al hacer clic en un muñeco, se revela instantáneamente el siguiente y, cuando llegamos al último muñeco, podemos restablecer el formulario para comenzar de nuevo. Eso es lo que queremos aquí.

Los mecánicos

Vamos a animar los muñecos en base a un punto central. Nuestra animación constará de muchos pasos:

  1. Desliza la muñeca actual hacia la izquierda.
  2. Abre la muñeca para revelar la siguiente.
  3. Mueve la siguiente muñeca donde comenzó la actual.
  4. Haz que la muñeca actual se desvanezca.
  5. Asigna la siguiente muñeca como muñeca actual.

Comencemos deslizando la muñeca actual hacia la izquierda. Aplicamos una animación cuando hacemos clic en una etiqueta. Usando el :checkedpseudo-selector podemos apuntar a la muñeca actual. En este punto, vale la pena señalar que usaremos variables CSS para controlar la velocidad y el comportamiento de la animación. Esto facilitará el encadenamiento de animaciones en las etiquetas.

:root {  --speed: 0.25;  --base-slide: 100;  --slide-distance: 60;}input:checked + label {  animation: slideLeft calc(var(--speed) * 1s) forwards;}@keyframes slideLeft {  to {    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -1%), 0);  }}

Eso se ve genial. Pero hay un problema. Tan pronto como hagamos clic en una etiqueta, podremos hacer clic nuevamente y restablecer la animación. No queremos que eso suceda.

¿Cómo podemos solucionar esto? Podemos eliminar eventos de puntero de una etiqueta una vez que se hace clic en ella.

input:checked + label {  animation: slideLeft calc(var(--speed) * 1s) forwards;  pointer-events: none;}

¡Excelente! Ahora, una vez que hemos comenzado, no podemos detener la cadena de animación.

A continuación, debemos abrir la muñeca para revelar la siguiente. Aquí es donde las cosas se complican porque vamos a necesitar algunos elementos adicionales, no sólo para crear el efecto de que el muñeco se abre, sino también para revelar el siguiente muñeco que está dentro. Así es: necesitamos duplicar la muñeca interior. El truco aquí consiste en revelar un muñeco "falso" que cambiamos por el real una vez que lo hemos animado. Esto también significa retrasar la revelación de la próxima etiqueta.

Ahora nuestro marcado actualiza las etiquetas para que contengan elementos de extensión.

label for="doll--1"  span/span  spanTop/span  spanBottom/span/label

Estos actuarán como muñeco “ficticio”, así como como tapa y base para el muñeco actual.

.doll {  color: #fff;  cursor: pointer;  height: 200px;  font-size: 2rem;  position: absolute;  text-align: center;  width: 100px;}.doll:nth-of-type(even) {  --bg: #00f;  --dummy-bg: #f00;}.doll:nth-of-type(odd) {  --bg: #f00;  --dummy-bg: #00f;}.doll__half {  background: var(--bg);  position: absolute;  width: 100%;  height: 50%;  left: 0;}.doll__half--top {  top: 0;}.doll__half--bottom {  bottom: 0;}.doll__dummy {  background: var(--dummy-bg);  height: 100%;  width: 100%;  position: absolute;  top: 0;  left: 0;}

La tapa requiere tres traslaciones para crear el efecto de apertura: una para abrirla, otra para moverla hacia la izquierda y otra para abrirla hacia abajo.

@keyframes open {  0% {    transform: translate(0, 0);  }  33.333333333333336% {    transform: translate(0, -100%);  }  66.66666666666667% {    transform: translate(-100%, -100%);  }  100% {    transform: translate(-100%, 100%);  }}

A continuación es donde podemos usar las propiedades personalizadas de CSS para manejar los valores cambiantes. Una vez que el muñeco se haya deslizado hacia la izquierda, podremos abrirlo. Pero, ¿cómo sabemos cuánto tiempo debemos demorar su apertura hasta que eso suceda? Podemos usar la --speedpropiedad personalizada que definimos anteriormente para calcular el retraso correcto.

Parece un poco rápido si usamos el --speedvalor tal como está, así que multiplíquelo por dos segundos:

input:checked + .doll {  animation: slideLeft calc(var(--speed) * 1s) forwards;  pointer-events: none;}input:checked + .doll .doll__half--top {  animation: open calc(var(--speed) * 2s) calc(var(--speed) * 1s) forwards; // highlight}

Mucho mejor:

Ahora necesitamos mover el muñeco “ficticio” interior a la nueva posición. Esta animación es como la animación abierta en que consta de tres etapas. Nuevamente, hay uno para subir, otro para mover hacia la derecha y otro para bajar. También es como la animación de diapositivas. Usaremos propiedades personalizadas de CSS para determinar la distancia que se mueve la muñeca.

:root {  // Introduce a new variable that defines how high the dummy doll should pop out.  --pop-height: 60;}@keyframes move {  0% {    transform: translate(0, 0) translate(0, 0);  }  33.333333333333336% {    transform: translate(0, calc(var(--pop-height) * -1%)) translate(0, 0);  }  66.66666666666667% {    transform: translate(0, calc(var(--pop-height) * -1%)) translate(calc((var(--base-slide) * 1px) + var(--slide-distance) * 1%), 0);  }  100% {    transform: translate(0, calc(var(--pop-height) * -1%)) translate(calc((var(--base-slide) * 1px) + var(--slide-distance) * 1%), calc(var(--pop-height) * 1%));  }}

¡Casi llegamos!

Lo único es que la siguiente muñeca estará disponible tan pronto como hagamos clic en una muñeca. eso significa que podemos hacer clic spam en nuestro camino a través del conjunto.

Técnicamente, la siguiente muñeca no debería aparecer hasta que la “falsa” se haya colocado en su lugar. Sólo una vez que la muñeca “falsa” esté en su lugar podremos esconderla y revelar la real. ¡Eso significa que usaremos animaciones a escala de cero segundos! Así es. Podemos jugar a fingir retrasando dos animaciones de cero segundos y usando animation-fill-mode.

@keyframes appear {  from {    transform: scale(0);  }}

En realidad, solo necesitamos un conjunto de @keyframes. porque podemos reutilizar lo que tenemos para crear el movimiento opuesto animation-direction: reverse. Con eso en mente, todas nuestras animaciones se aplican de esta manera:

// The next dollinput:checked + .doll + input + .doll,// The last doll (doesn't have an input)input:checked + .doll + .doll {  animation: appear 0s calc(var(--speed) * 5s) both;  display: block;}// The current dollinput:checked + .doll,// The current doll that isn't the first. Specificity prevailsinput:checked + .doll + input:checked + .doll {  animation: slideLeft calc(var(--speed) * 1s) forwards;  pointer-events: none;}input:checked + .doll .doll__half--top,input:checked + .doll + input:checked + .doll .doll__half--top {  animation: open calc(var(--speed) * 2s) calc(var(--speed) * 1s) forwards;}input:checked + .doll .doll__dummy,input:checked + .doll + input:checked + .doll .doll__dummy {  animation: move calc(var(--speed) * 2s) calc(var(--speed) * 3s) forwards, appear 0s calc(var(--speed) * 5s) reverse forwards;}

Tenga en cuenta lo importantes que son las variables, especialmente cuando encadenamos animaciones. Eso nos lleva casi a donde necesitamos estar.

Ahora puedo oírlo: "¡Son todos del mismo tamaño!" Sí. Esa es la pieza que falta. Necesitan reducir su escala. El truco aquí consiste en ajustar el marcado nuevamente y utilizar las propiedades personalizadas de CSS una vez más.

input type="checkbox"/label for="doll--0"  span    span/span  /span //highlight  span    span/span    span/span  /span/label    

Acabamos de introducir una propiedad personalizada CSS en línea que nos indica el índice de la muñeca. Podemos usar esto para generar una escala de cada mitad, así como de la muñeca interior falsa. Las mitades tendrán que escalarse para que coincidan con el tamaño real de la muñeca, pero la escala interior falsa de la muñeca deberá coincidir con la de la siguiente muñeca. ¡Complicado!

Podemos aplicar estas escalas dentro de los contenedores para que nuestras animaciones no se vean afectadas.

:root {  --scale-step: 0.05;}.doll__container,.doll__dummy-container {  height: 100%;  left: 0;  position: absolute;  top: 0;  width: 100%;}.doll__container {  transform: scale(calc(1 - ((var(--doll-index)) * var(--scale-step))));  transform-origin: bottom;}.doll__dummy {  height: 100%;  left: 0;  position: absolute;  top: 0;  transform: scale(calc(1 - ((var(--doll-index) + 1) * var(--scale-step))));  transform-origin: bottom center;  width: 100%;}  

Observe cómo la .doll__dummyclase utiliza var(--doll-index) + 1)para calcular la escala para que coincida con la siguiente muñeca.

Por último, reasignamos la animación a la .doll__dummy-containerclase en lugar de a la .doll__dummyclase.

input:checked + .doll .doll__dummy-container,input:checked + .doll + input:checked + .doll .doll__dummy-container {  animation: move calc(var(--speed) * 2s) calc(var(--speed) * 3s) forwards, appear 0s calc(var(--speed) * 5s) reverse forwards;}

Aquí hay una demostración en la que a los contenedores se les ha dado un color de fondo para ver qué está sucediendo.

Podemos ver que, aunque el tamaño del contenido cambia, estos siguen siendo del mismo tamaño. Esto genera un comportamiento de animación consistente y hace que el código sea más fácil de mantener.

Últimos retoques

¡Vaya, las cosas se ven muy bien! ¡Todo lo que necesitamos son algunos toques finales y listo!

La escena comienza a verse desordenada porque estamos apilando las muñecas "viejas" a un lado cuando se introduce una nueva. Así que saquemos una muñeca de la vista cuando se revele la siguiente para limpiar ese desastre.

@keyframes slideOut {  from {    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -1%), 0);  }  to {    opacity: 0;    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -2%), 0);  }}input:checked + .doll,input:checked + .doll + input:checked + .doll {  animation: slideLeft calc(var(--speed) * 1s) forwards,    slideOut calc(var(--speed) * 1s) calc(var(--speed) * 6s) forwards;  pointer-events: none;}

La nueva slideOutanimación desvanece el muñeco mientras se traslada hacia la izquierda. Perfecto.

Eso es todo el truco de CSS que necesitamos para que este efecto funcione. Todo lo que queda es diseñar los muñecos y la escena.

Tenemos muchas opciones para estilizar las muñecas. Podríamos usar una imagen de fondo, una ilustración CSS, SVG o lo que sea. ¡Incluso podríamos crear algunos muñecos emoji que usen tonos en línea aleatorios!

Vayamos con SVG en línea.

Básicamente estoy usando la misma mecánica subyacente que ya hemos cubierto. La diferencia es que también estoy generando variables en línea para el tono y la luminosidad para que los osos luzcan diferentes colores de camiseta.


¡Ahí lo tenemos! Apilando muñecos (err, osos) ¡con nada más que HTML y CSS! Todo el código para todos los pasos está disponible en esta colección CodePen. ¿Preguntas o sugerencias? No dudes en comunicarte conmigo aquí en los comentarios.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

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

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion