CSS en 3D: aprende a pensar en cubos en lugar de cajas
Mi camino para aprender CSS fue un poco heterodoxo. No comencé como desarrollador front-end. Yo era un desarrollador de Java. De hecho, mis primeros recuerdos de CSS fueron elegidos colores para cosas en Visual Studio.
No fue hasta más tarde que pude abordar y encontrar mi amor por la parte delantera. Y la exploración de CSS llegó más tarde. Cuando lo hizo, fue en el momento en que CSS3 estaba despegando. El 3D y la animación eran los chicos geniales del barrio. Casi dieron forma a mi aprendizaje de CSS. Me atrajeron y moldearon (nunca mejor dicho) mi comprensión de CSS más que otras cosas, como el diseño, el color, etc.
Lo que quiero decir es que he estado haciendo todo el asunto del CSS 3D por un minuto. Y como ocurre con cualquier cosa en la que pasas mucho tiempo, terminas refinando tu proceso a lo largo de los años a medida que perfecciona esa habilidad. ¡Este artículo es un vistazo a cómo estoy abordando CSS 3D actualmente y repasa algunos consejos y trucos que podrían ayudarte!
Todo es un cuboide
Para la mayoría de las cosas, podemos usar un cuboide. Podemos crear formas más complejas, seguras, pero normalmente requieren un poco más de consideración. Las curvas son particularmente difíciles y existen algunos trucos para manejarlas (pero hablaremos de eso más adelante).
No vamos a explicar cómo crear un cuboide en CSS. Podemos consultar la publicación de Ana Tudor para eso, o ver este screencast mío haciendo uno:
En esencia, utilizamos un elemento para envolver nuestro cuboide y luego transformamos seis elementos dentro. Cada elemento actúa como un lado de nuestro cuboide. Es importante que lo apliquemos transform-style: preserve-3d
. Y no es mala idea aplicarlo en todas partes. Es probable que nos ocupemos de cuboides anidados cuando las cosas se vuelvan más complejas. Intentar corregir un error transform-style
mientras se salta entre navegadores puede resultar complicado.
* { transform-style: preserve-3d; }
Para tus creaciones 3D que son más que unas pocas caras, intenta imaginar la escena completa construida a partir de cuboides. Para ver un ejemplo real, considere esta demostración de un libro en 3D. Son cuatro cuboides. Uno para cada portada, uno para el lomo y otro para las páginas. El uso de background-image
hace el resto por nosotros.
Estableciendo una escena
Usaremos cuboides como piezas de LEGO. Pero podemos hacernos la vida un poco más fácil preparando un escenario y creando un avión. Ese plano es donde se asentará nuestra creación y nos facilitará rotar y mover toda la creación.
Para mí, cuando creo una escena, primero me gusta rotarla en los ejes X e Y. Luego lo dejo plano con rotateX(90deg)
. De esa manera, cuando quiero agregar un nuevo cuboide a la escena, lo agrega dentro del elemento plano. Otra cosa que haré aquí es establecer position: absolute
todos los cuboides.
.plane { transform: rotateX(calc(var(--rotate-x, -24) * 1deg)) rotateY(calc(var(--rotate-y, -24) * 1deg)) rotateX(90deg) translate3d(0, 0, 0);}
Comience con un texto estándar
La creación de cuboides de varios tamaños ya lo largo de un plano genera muchas repeticiones en cada creación. Por esta razón, uso Pug para crear mis cuboides mediante un mixin. Si no te conoces con Pug, escribe una introducción de 5 minutos.
Una escena típica se ve así:
//- Front//- Back//- Right//- Left//- Top//- Bottommixin cuboid(className) .cuboid(class=className) - let s = 0 while s 6 .cuboid__side - s++.scene //- Plane that all the 3D stuff sits on .plane +cuboid('first-cuboid')
En cuanto al CSS. Mi clase cuboide actualmente se ve así:
.cuboid { // Defaults --width: 15; --height: 10; --depth: 4; height: calc(var(--depth) * 1vmin); width: calc(var(--width) * 1vmin); transform-style: preserve-3d; position: absolute; font-size: 1rem; transform: translate3d(0, 0, 5vmin);}.cuboid div:nth-of-type(1) { height: calc(var(--height) * 1vmin); width: 100%; transform-origin: 50% 50%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotateX(-90deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin));}.cuboid div:nth-of-type(2) { height: calc(var(--height) * 1vmin); width: 100%; transform-origin: 50% 50%; transform: translate(-50%, -50%) rotateX(-90deg) rotateY(180deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin)); position: absolute; top: 50%; left: 50%;}.cuboid div:nth-of-type(3) { height: calc(var(--height) * 1vmin); width: calc(var(--depth) * 1vmin); transform: translate(-50%, -50%) rotateX(-90deg) rotateY(90deg) translate3d(0, 0, calc((var(--width) / 2) * 1vmin)); position: absolute; top: 50%; left: 50%;}.cuboid div:nth-of-type(4) { height: calc(var(--height) * 1vmin); width: calc(var(--depth) * 1vmin); transform: translate(-50%, -50%) rotateX(-90deg) rotateY(-90deg) translate3d(0, 0, calc((var(--width) / 2) * 1vmin)); position: absolute; top: 50%; left: 50%;}.cuboid div:nth-of-type(5) { height: calc(var(--depth) * 1vmin); width: calc(var(--width) * 1vmin); transform: translate(-50%, -50%) translate3d(0, 0, calc((var(--height) / 2) * 1vmin)); position: absolute; top: 50%; left: 50%;}.cuboid div:nth-of-type(6) { height: calc(var(--depth) * 1vmin); width: calc(var(--width) * 1vmin); transform: translate(-50%, -50%) translate3d(0, 0, calc((var(--height) / 2) * -1vmin)) rotateX(180deg); position: absolute; top: 50%; left: 50%;}
Lo cual, por defecto, me da algo como esto:
Desarrollado por variables CSS
Es posible que hayas notado algunas variables CSS (también conocidas como propiedades personalizadas) allí. Esto supone un gran ahorro de tiempo. Estoy alimentando mis cuboides con variables CSS.
--width
: El ancho de un cuboide en el plano.--height
: La altura de un cuboide en el plano.--depth
: La profundidad de un cuboide en el plano.--x
: La posición X en el avión.--y
: La posición Y en el avión
Lo uso vmin
principalmente como unidad de tamaño para que todo responda. Si estoy creando algo a escala, podría crear una unidad responsiva. Mencionamos esta técnica en un artículo anterior. De nuevo, deja el avión en el suelo. Ahora puedo referirme a mis cuboides como si tuvieran altura, ancho y profundidad. Esta demostración muestra cómo podemos mover un cuboide por el plano cambiando sus dimensiones.
Depuración con dat.GUI
Es posible que hayas notado ese pequeño panel en la parte superior derecha para algunas de las demostraciones que hemos cubierto. Eso es todo. GUI. Es una biblioteca de controladores liviana para JavaScript que es muy útil para depurar CSS 3D. Sin mucho código, podemos configurar un panel que nos permita cambiar las variables CSS en el tiempo de ejecución. Una cosa que me gusta hacer es usar el panel para rotar el plano en los ejes X e Y. De esa manera, es posible ver cómo se alinean las cosas o cómo funcionan en una pieza que tal vez no veas al principio.
const { dat: { GUI },} = windowconst CONTROLLER = new GUI()const CONFIG = { 'cuboid-height': 10, 'cuboid-width': 10, 'cuboid-depth': 10, x: 5, y: 5, z: 5, 'rotate-cuboid-x': 0, 'rotate-cuboid-y': 0, 'rotate-cuboid-z': 0,}const UPDATE = () = { Object.entries(CONFIG).forEach(([key, value]) = { document.documentElement.style.setProperty(`--${key}`, value) })}const CUBOID_FOLDER = CONTROLLER.addFolder('Cuboid')CUBOID_FOLDER.add(CONFIG, 'cuboid-height', 1, 20, 0.1) .name('Height (vmin)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'cuboid-width', 1, 20, 0.1) .name('Width (vmin)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'cuboid-depth', 1, 20, 0.1) .name('Depth (vmin)') .onChange(UPDATE)// You have a choice at this point. Use x||y on the plane// Or, use standard transform with vmin.CUBOID_FOLDER.add(CONFIG, 'x', 0, 40, 0.1) .name('X (vmin)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'y', 0, 40, 0.1) .name('Y (vmin)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'z', -25, 25, 0.1) .name('Z (vmin)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'rotate-cuboid-x', 0, 360, 1) .name('Rotate X (deg)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'rotate-cuboid-y', 0, 360, 1) .name('Rotate Y (deg)') .onChange(UPDATE)CUBOID_FOLDER.add(CONFIG, 'rotate-cuboid-z', 0, 360, 1) .name('Rotate Z (deg)') .onChange(UPDATE)UPDATE()
Si miras el video timelapse en este tweet. Notarás que girará mucho el avión a medida que construya la escena.
Wondering how it looked like to make that CSS house? 🤔
I wondered why the Mac was using more CPU than expected 🤔
Then I remembered. OBS was recording a timelapse for you! 😅
Here's a timelapse of creating the house from "Up" in CSS 😎
👉 https://t.co/tYroGaZ5mH via @CodePen pic.twitter.com/6HZwLjjzfI
— jhey ▲🐻🎈 (@jh3yy) October 2, 2020
Ese código dat.GUI es un poco repetitivo. Podemos crear funciones que tomarán una configuración y generarán el controlador. Se necesitan algunos retoques para satisfacer sus necesidades. Empecé a jugar con controladores generados dinámicamente en esta demostración.
centrado
Es posible que hayas notado que, de forma predeterminada, cada cuboide está la mitad debajo y la otra mitad encima del plano. Eso es intencional. También es algo que comencé a hacer recientemente. ¿Por qué? Porque queremos utilizar el elemento contenedor de nuestros cuboides como centro del cuboide. Esto facilita la animación. Especialmente, si estamos considerando girar alrededor del eje Z. Descubrí esto al crear “CSS is Cake”. Después de hacer el pastel, decidí que quería que cada porción fuera interactiva. Luego tuve que regresar y cambiar mi implementación para arreglar el centro de rotación del corte volteado.
Aquí he desglosado esa demostración para mostrar los centros y cómo tener un centro desplazado afectaría la demostración.
Posicionamiento
Si estamos trabajando con una escena más compleja, podemos dividirla en diferentes secciones. Aquí es donde resulta útil el concepto de subplanos. Considere esta demostración en la que he recreado mi espacio de trabajo personal.
Sala de estudio CSS 3D
¡Está hecho! Una recreación CSS de mi espacio de trabajo.
https://t.co/OGUfmPfGJH vía @CodePen pic.twitter.com/hxYBSbUVJZ
– jhey ▲ (@jh3yy) 28 de septiembre de 2020
Están sucediendo muchas cosas aquí y es difícil realizar un seguimiento de todos los cuboides. Para eso, podemos introducir subplanos. Analicemos esa demostración. La silla tiene su propio subplano. Esto hace que sea más fácil moverlo por la escena y rotarlo, entre otras cosas, sin afectar nada más. De hecho, ¡incluso podemos girar la peonza sin mover los pies!
Estética
Una vez que tenemos una estructura, es hora de trabajar en la estética. Todo esto depende de lo que estés haciendo. Pero puedes obtener algunos beneficios rápidos utilizando ciertas técnicas. Tiendo a empezar haciendo las cosas “feas”, luego vuelvo y creo variables CSS para todos los colores y las aplico. Tres tonos para una determinada cosa nos permiten diferenciar visualmente los lados de un cuboide. Considere este ejemplo de tostadora. Tres tonos cubren los laterales de la tostadora:
Nuestro mixin Pug de antes nos permite definir nombres de clases para un cuboide. Aplicar color a un lado generalmente se ve así:
/* The front face uses a linear-gradient to apply the shimmer effect */.toaster__body div:nth-of-type(1) { background: linear-gradient(120deg, transparent 10%, var(--shine) 10% 20%, transparent 20% 25%, var(--shine) 25% 30%, transparent 30%), var(--shade-one);}.toaster__body div:nth-of-type(2) { background: var(--shade-one);}.toaster__body div:nth-of-type(3),.toaster__body div:nth-of-type(4) { background: var(--shade-three);}.toaster__body div:nth-of-type(5),.toaster__body div:nth-of-type(6) { background: var(--shade-two);}
Es un poco complicado incluir elementos adicionales con nuestra mezcla de Pug. Pero no olvidemos que cada lado de nuestro cuboide ofrece dos pseudoelementos. Podemos usarlos para varios detalles. Por ejemplo, la ranura para la tostadora y la ranura para el asa lateral son pseudoelementos.
Otro truco es utilizarlo background-image
para agregar detalles. Por ejemplo, considere el espacio de trabajo 3D. Podemos usar capas de fondo para crear sombreado. Podemos utilizar imágenes reales para crear superficies texturizadas. El suelo y la alfombra son una repetición background-image
. De hecho, usar un pseudoelemento para texturas es genial porque luego podemos transformarlas si es necesario, como rotar una imagen en mosaico. También descubrí que en algunos casos parpadeo al trabajar directamente con un lado cuboide.
Un problema al usar una imagen como textura es cómo creamos diferentes tonos. Necesitamos matices para diferenciar los diferentes lados. Ahí es donde la filter
propiedad puede ayudar. Aplicar un brightness``()
filtro a los diferentes lados de un cuboide puede aclararlos u oscurecerlos. Considere esta tabla invertida de CSS. Todas las superficies utilizan una imagen de textura. Pero para diferenciar los lados se aplican filtros de brillo.
Perspectiva de humo y espejos.
¿Qué tal las formas (o características que queremos crear y que parecen imposibles) utilizando un conjunto finito de elementos? A veces podemos engañar a la vista con un poco de humo y espejos. Podemos proporcionar una sensación de 3D "falsa". La biblioteca Zdog lo hace bien y es un buen ejemplo de ello.
Considere este paquete de globos. Las cuerdas que los sujetan utilizan la perspectiva correcta y cada una tiene su propia rotación, inclinación, etc. Pero los globos en sí son planos. Si rotamos el avión, los globos mantienen la rotación del contraplano. Y esto da esa impresión 3D "falsa". Pruebe la demostración y desactive el contador.
A veces es necesario pensar un poco de forma innovadora. Me sugirieron una planta de interior mientras construía el espacio de trabajo 3D. Tengo algunos en la habitación. Mi pensamiento inicial fue: “No, puedo hacer una maceta cuadrada y ¿cómo haría todas las hojas?” Bueno, en realidad, también podemos usar algunos trucos oculares en este caso. Tome una imagen de archivo de algunas hojas o una planta. Elimina el fondo con una herramienta como remove.bg. Luego, coloque muchas imágenes en el mismo lugar, pero gírelas cada una de ellas una cierta cantidad. Ahora, cuando se giran, tenemos la impresión de una planta en 3D.
Abordar formas incómodas
Las formas incómodas son difíciles de cubrir de forma genérica. Cada creación tiene sus propios obstáculos. Pero hay un par de ejemplos que podrían darle ideas para abordar las cosas. Recientemente leí un artículo sobre la UX de los paneles de interfaz LEGO. De hecho, abordar el trabajo de CSS 3D como si fuera un set de LEGO no es una mala idea. Pero el panel de interfaz LEGO es una forma que podríamos hacer con CSS (menos los montantes; hace poco supe que así se llaman). Para empezar, es un cuboide. Luego podemos recortar la cara superior, hacer que la cara del extremo sea transparente y rotar un pseudoelemento para unirlo. Podemos usar el pseudoelemento para agregar detalles con algunas capas de fondo. Intente activar y desactivar la estructura alámbrica en la demostración a continuación. Si queremos las alturas y ángulos exactos de las caras, podemos usar algunas matemáticas para ejercitar la hipotenusa, etc.
Otra cosa incómoda de cubrir son las curvas. Las formas esféricas no están en la timonera de CSS. Tenemos varias opciones en este punto. Una opción es aceptar ese hecho y crear polígonos con un número finito de lados. Otra es crear formas redondeadas y utilizar el método de rotación que mencionamos con la planta. Cada una de estas opciones podría funcionar. Pero nuevamente, se trata de casos de uso. Cada uno tiene pros y contras. Con el polígono renunciamos a las curvas o utilizamos tantos elementos que obtenemos una casi curva. Esto último podría provocar problemas de rendimiento. Con el truco de la perspectiva, también podemos acabar con problemas de rendimiento dependiendo. También renunciamos a poder diseñar los “lados” de la forma ya que no los hay.
Z peleando
Por último, pero no menos importante, vale la pena mencionar la “lucha Z”. Aquí es donde ciertos elementos de un plano pueden superponerse o provocar un parpadeo no deseado. Es difícil dar buenos ejemplos de esto. No existe una solución genérica para ello. Es algo que hay que abordar caso por caso. La estrategia principal es ordenar las cosas en el DOM según corresponda. Pero a veces ese no es el único problema.
Ser preciso a veces puede causar problemas. Volvamos a referirnos al espacio de trabajo 3D. Considere el lienzo en la pared. La sombra es un pseudoelemento. Si colocamos el lienzo exactamente contra la pared, nos vamos a encontrar con problemas. Si hacemos eso, la sombra y la pared van a luchar por la posición delantera. Para combatir esto, podemos traducir las cosas un poco. Eso resolverá el problema y declarará lo que debería sentarse al frente.
Intente cambiar el tamaño de esta demostración con el "Desplazamiento del lienzo" activado y desactivado. ¿Observas cómo la sombra parpadea cuando no hay compensación? Eso es porque la sombra y la pared luchan por la vista. El desplazamiento establece el --x
en una fracción de 1vmin
lo que hemos nombrado --cm
. Esa es una unidad receptiva que se utiliza para esa creación.
Eso es todo"!
Lleva tu CSS a otra dimensión. ¡Usa algunos de mis consejos, crea los tuyos propios, compártelos y comparte tus creaciones 3D! Sí, hacer cosas 3D en CSS puede ser difícil y definitivamente es un proceso que podemos perfeccionar a medida que avanzamos. Diferentes enfoques funcionan para diferentes personas y la paciencia es un ingrediente necesario. ¡Me interesa ver dónde llevas tu enfoque!
¿La cosa más importante? ¡Diviértete con eso!
Deja un comentario