CSS le permite crear diseños e interfaces dinámicos en la web, pero como lenguaje, es estático: una vez que se establece un valor, no se puede cambiar. La idea de la aleatoriedad está fuera de la mesa. La generación de números aleatorios en tiempo de ejecución es territorio de JavaScript, no tanto de CSS. ¿O es eso? Si tenemos en cuenta una pequeña interacción del usuario, en realidad podemos generar cierto grado de aleatoriedad en CSS. ¡Vamos a ver!
Aleatorización de otros idiomas
Hay formas de obtener algo de “aleatorización dinámica” utilizando variables CSS, como explica Robin Rendle en un artículo sobre Programar Plus. Pero estas soluciones no son 100% CSS, ya que requieren JavaScript para actualizar la variable CSS con el nuevo valor aleatorio.
Podemos utilizar preprocesadores como Sass o Less para generar valores aleatorios, pero una vez compilado y exportado el código CSS, los valores se fijan y se pierde la aleatoriedad. Como explica Jake Albaugh:
aleatorio con descaro es como elegir al azar el nombre de un personaje principal en una historia. solo es aleatorio cuando se escribe. no cambia.
– jake albaugh (@jake_albaugh) 29 de diciembre de 2016
¿Por qué me preocupan los valores aleatorios en CSS?
En el pasado, desarrollé aplicaciones simples solo de CSS, como un juego de preguntas, un juego de Simon y un truco de magia. Pero quería hacer algo un poco más complicado. Dejaré una discusión sobre la validez, la utilidad o la practicidad de crear estos fragmentos de código CSS para más adelante.
Partiendo de la premisa de que algunos juegos de mesa podrían representarse como máquinas de estado finito (FSM), podrían representarse utilizando HTML y CSS. Así que comencé a desarrollar un juego de Snakes and Ladders (también conocido como Chutes and Ladders). Es un juego sencillo. El objetivo es hacer avanzar un peón desde el principio hasta el final del tablero evitando las serpientes y tratando de subir las escaleras.
El proyecto parecía factible, pero había algo que me faltaba: dados rodantes!
El lanzamiento de dados (junto con el lanzamiento de una moneda) son universalmente reconocidos por su aleatorización. Lanzas los dados o la moneda y obtienes un valor desconocido cada vez.
Simulando una tirada de dados aleatoria
Iba a superponer capas con etiquetas y usar animaciones CSS para “rotar” e intercambiar qué capa estaba en la parte superior. Algo como esto:
Simulación de cómo se animan las capas en un navegador.
El código para imitar esta aleatorización no es excesivamente complicado y se puede lograr con una animación y diferentes retrasos de animación:
/* The highest z-index is the numbers of sides in the dice */
@keyframes changeOrder {
from { z-index: 6; }
to { z-index: 1; }
}
/* All the labels overlap by using absolute positioning */
label {
animation: changeOrder 3s infinite linear;
background: #ddd;
cursor: pointer;
display: block;
left: 1rem;
padding: 1rem;
position: absolute;
top: 1rem;
user-select: none;
}
/* Negative delay so all parts of the animation are in motion */
label:nth-of-type(1) { animation-delay: -0.0s; }
label:nth-of-type(2) { animation-delay: -0.5s; }
label:nth-of-type(3) { animation-delay: -1.0s; }
label:nth-of-type(4) { animation-delay: -1.5s; }
label:nth-of-type(5) { animation-delay: -2.0s; }
label:nth-of-type(6) { animation-delay: -2.5s; }
La animación se ha ralentizado para permitir una interacción más fácil (pero aún lo suficientemente rápida para ver el obstáculo que se explica a continuación). La pseudoaleatoriedad también es más clara.
Ver la pluma
Demostración de número generado pseudoaleatoriamente con CSS por Alvaro Montoro (@alvaromontoro)
en CodePen.
Pero luego me encontré con un obstáculo: estaba obteniendo números aleatorios, pero a veces, incluso cuando hacía clic en mis “dados”, no devolvía ningún valor.
Intenté aumentar los tiempos en la animación y eso pareció ayudar un poco, pero todavía tenía algunos valores inesperados.
Fue entonces cuando hice lo que la mayoría de los desarrolladores hacen cuando encuentran un obstáculo que no pueden resolver con solo buscar en línea: les pedí ayuda a otros desarrolladores en forma de una pregunta de StackOverflow.
Por suerte para mí, al siempre ingenioso Temani Afif se le ocurrió una explicación y una solución.
Para simplificar un poco, el problema era que el navegador solo activa el evento clic / presionar cuando el elemento que está activo al presionar el mouse es el mismo elemento que está activo al presionar el mouse.
Debido a la animación giratoria, la etiqueta superior del mouse hacia abajo no era la etiqueta superior del mouse hacia arriba, a menos que lo hiciera lo suficientemente rápido o lento para que la animación girara. Es por eso que el aumento de los tiempos de animación ocultó estos problemas.
La solución fue aplicar una posición de “estática” para romper el contexto de apilamiento y usar un pseudoelemento como ::before
o ::after
con un mayor z-index
para ocupar su lugar. De esta manera, la etiqueta activa siempre estaría en la parte superior cuando se subiera el mouse.
/* The active tag will be static and moved out of the window */
label:active {
margin-left: 200%;
position: static;
}
/* A pseudo-element of the label occupies all the space with a higher z-index */
label:active::before {
content: "";
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 10;
}
Aquí está el código con la solución con un tiempo de animación más rápido:
Ver la pluma
Demostración de número generado pseudoaleatoriamente con CSS por Alvaro Montoro (@alvaromontoro)
en CodePen.
Después de hacer este cambio, lo único que quedaba era crear una pequeña interfaz para dibujar un dado falso para hacer clic, y se completó el CSS Snakes and Ladders.
Esta técnica tiene algunos inconvenientes obvios.
- Requiere la entrada del usuario: se debe hacer clic en una etiqueta para activar la “generación de números aleatorios”.
- No escala bien: Funciona muy bien con pequeños conjuntos de valores, pero es una molestia para grandes rangos.
- No es realmente aleatorio, sino pseudoaleatorio: una computadora podría detectar fácilmente qué valor se generaría en cada momento.
Pero, por otro lado, es 100% CSS (sin necesidad de preprocesadores u otros ayudantes externos) y, para un usuario humano, puede parecer 100% aleatorio.
Y hablando de manos … Este método puede usarse no solo para números aleatorios sino para cualquier cosa aleatoria. En este caso, lo usamos para elegir “al azar” la elección de la computadora en un juego Piedra-Papel-Tijera:
Ver la pluma
CSS Piedra-Papel-Tijeras de Alvaro Montoro (@alvaromontoro)
en CodePen.