Creación de una galería de imágenes de cubo 3D | Programar Plus

La siguiente es una publicación invitada de Kushagra Gour (@chinchang457). Kushagra me escribió para mostrarme una divertida demostración interactiva que hizo. Toca muchos de los conceptos de transformaciones 3D en CSS, un tema que no hemos cubierto mucho aquí. Así que aquí está Kushagra tomando las riendas para explicar estos conceptos a través de una demostración.

Recientemente rediseñé mi sitio web y se me ocurrió una idea de cubo 3D de 2 caras para la página de inicio y el encabezado. Al pasar el mouse, gira entre mi imagen de pantalla y el enlace de Twitter. Mientras lo hacía, pensé por qué no extender la idea a un cubo completo de 6 caras que podría usarse como una galería de imágenes. ¡Esto es lo que se me ocurrió!

Vea la galería de imágenes del cubo Pen 3D de Kushagra Gour (@chinchang) en CodePen

Este tutorial describe cómo puedes hacer algo como esto, enfatizando los conceptos 3D de CSS3.

rompiendo el cubo

Es bastante evidente al ver el cubo que las 6 caras del cubo serían 6 elementos HTML diferentes. Seis <div> elementos en este caso. Debido a que deben girarse como un cubo, deben estar en un elemento contenedor. Si codificamos esta estructura básica, tendríamos algo como esto:

<div class="cube">
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
 </div>

Además, dado que necesitaremos hacer referencia a cada lado para diseñarlos, debemos agregarles las clases apropiadas.

<div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
 </div>

Estilizando las caras

No vemos nada todavía. Así que demos algo de dimensión y estilo a las caras.

$size: 150px; // cube length
.cube {
  width: $size;
  height: $size;
  position: relative;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.5;
}

Vea el tutorial de la galería de cubos Pen 3d: P1 de Kushagra Gour (@chinchang) en CodePen

Tenga en cuenta que cada cara del cubo tiene position ajustado a absolute por lo que se apilan unos sobre otros en un solo lugar. Ahora podemos seleccionar cada uno y colocarlos en consecuencia.

También le he dado opacidad a cada rostro para que podamos ver a través de ellos y ver lo que está pasando.

Conceptos 3D CSS3

Conozcamos algunos conceptos de CSS3 3D. Para acercar un poco más la cara frontal a los ojos, la trasladamos en el eje Z:

.cube-face-front {
  color: blue;
  transform: translate3d(0, 0, 20px);
}

No notarás ninguna diferencia todavía. Entendamos por qué.

perspectiva

Como se menciona en MDN:

La propiedad CSS de perspectiva determina la distancia entre el plano z=0 y el usuario para dar cierta perspectiva al elemento posicionado en 3D.

En términos simples, su valor determina la cantidad de 3D en el espacio. Cuanto menor es el valor de esta propiedad, más profundo es el efecto 3D. Sin esta propiedad, los elementos se representan en la pantalla utilizando la proyección paralela en la que las líneas de proyección son paralelas entre sí. Por lo tanto, no importa cuánto se acerque o se aleje un elemento de usted, seguirá pareciendo tener el mismo tamaño a diferencia del mundo real. (Más información en perspectiva).

Estableceremos esta propiedad en el contenedor principal de nuestro cubo para que todos sus elementos secundarios (caras) se vean afectados por una perspectiva común, así:

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
}

Como era de esperar, ahora la cara frontal parece más grande. Pero todavía falta algo.

estilo de transformación

Incluso después de dar perspectiva a nuestra escena, todavía tenemos un problema. Idealmente, la cara frontal debería aparecer por encima de todas las demás caras, escondiéndolas detrás de ella. Pero no lo es.

El motivo es que nuestro contenedor de cubos no tiene un contexto de representación 3D que se define de la siguiente manera en CSSWG:

Una jerarquía de bloques contenedora de uno o más niveles, instanciada por elementos con un valor calculado para la propiedad ‘transform-style’ de ‘preserve-3d’, cuyos elementos comparten un sistema de coordenadas tridimensional común.

Sin un elemento que tenga la transform-style propiedad establecida como preserve-3d, sus elementos secundarios se representan aplanados, sin contexto de apilamiento. Por lo tanto, incluso cuando compramos la cara frontal más cerca del eje Z, continuó renderizándola en su índice z original sin tener en cuenta su posición en el espacio 3D.

Trate de dar la .cube elemento de esta propiedad y ver qué sucede.

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}

Funcionó. Ahora que tenemos la configuración de nuestro sistema 3D, ¡transformemos esto en un cubo!

Posicionamiento de las caras

Tomaremos una cara a la vez y la colocaremos en su posición apropiada. Primero comprendamos el sistema de coordenadas en CSS. Si tuviéramos que tomar una de nuestras caras de cubo, sería algo como esto:

Cara frontal acostada con el eje Z viniendo hacia nosotros

Como puede ver, el eje Z sale de la pantalla directamente desde el elemento. Por lo tanto, cuando trasladamos la cara frontal positivamente en el eje Z, aparece más cerca de nosotros. Un punto a tener en cuenta aquí es que este sistema de coordenadas es local para este elemento. Miremos eso más de cerca.

Usaremos nuestra cara frontal nuevamente y la rotaremos un poco sobre su eje Y.

.cube-face-front {
  transform: rotateY(40deg);
}

Ahora así es como se ve nuestra cara frontal después de la rotación:

Los ejes giran con la cara.

Observe cómo los ejes giraron junto con el elemento. Esto significa que el eje Z ya no viene directamente hacia nosotros. En cambio, está en la dirección del elemento. Entonces, si lo moviera a lo largo del eje Z ahora, se movería en la dirección en la que mira el elemento.

Este es el concepto que usaremos para posicionar cada cara de nuestro cubo. Suponga que el centro del cubo está en el lugar 2D de la pantalla. Las caras del cubo deben colocarse alrededor de él en el espacio 3D. Recordar, Giramos la cara para que mire en la dirección requerida y luego la trasladamos en el eje Z.

Cara frontal

Aquí no hay nada que rotar. Simplemente mueva la cara frontal hacia adelante la mitad de la longitud del cubo.

.cube-face-front {
  transform: translate3d(0, 0, $size/2);
}

Cara posterior

La cara trasera estará orientada en dirección opuesta a la delantera. Lo que significa que necesita ser girado por 180 degrees sobre el eje Y antes de traducir así:

.cube-face-back {
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
}

2 lados listos. 4 más para ir.

cara izquierda

En caso de que aún tenga dudas sobre cómo se está realizando la transformación, entendamos esta cara a través de algunas imágenes.

Así es como está ahora la cara izquierda, plana en el plano 2D (z=0):

Cara izquierda: en el plano 2d

Como el lado izquierdo debe mirar hacia la izquierda, le damos una rotación de 90 degrees:

Cara izquierda: después de la rotación

Y como toda cara, la movemos sobre su eje Z:

Cara izquierda: después de la traducción

Este es el CSS final para la cara izquierda:

.cube-face-left {
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}

cara derecha

Esto es similar a la cara izquierda, excepto por una rotación positiva:

.cube-face-right {
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
}

cara superior

Esta cara necesita ser girada sobre el eje X por 90 degrees para que mire hacia arriba:

.cube-face-top {
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
}

Cara inferior

Del mismo modo, dando una rotación negativa posicionamos la cara inferior:

.cube-face-bottom {
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

Esto completa el posicionamiento de las caras y ya tenemos nuestro cubo completo con el siguiente CSS final (también he añadido colores aleatorios a cada cara para diferenciarlas):

$size: 150px; // cube length
.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
} 
.cube-face-back {
  background: orange;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

Ahora, para rotar el cubo, simplemente podemos aplicar rotaciones en el .cube elemento. Intenta darle una rotación de 180 degrees alrededor del eje Y (verticalmente):

.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

Deberías tener algo como:

Vea el tutorial de la galería de cubos Pen 3d: P2 de Kushagra Gour (@chinchang) en CodePen

¿Notas algo mal? Giramos el cubo 180 degrees alrededor de su eje vertical. Deberíamos haber visto la cara posterior en lugar de la cara frontal ahora. Lo vemos, pero se muestra más pequeño por alguna razón. que hicimos mal?

Fijando la perspectiva

Si recuerdas, le dimos la propiedad de perspectiva al contenedor del cubo (.cube). Y cuando giramos ese elemento hace un momento, la perspectiva marcada por el punto de fuga también giró junto con él. Entonces, el punto de fuga que inicialmente estaba en algún lugar detrás del lugar 2D se colocó frente al plano 2D después de la rotación, lo que causó el problema.

Lo que idealmente queremos es que la perspectiva nunca cambie y permanezca constante sin importar qué elemento transformemos.

¿Cómo arreglamos esto? Envolvemos todos nuestros elementos con otro DIV a la que dar la perspective propiedad:

<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>
.scene {
  margin: 100px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

Verifique el resultado ahora y todo debería aparecer como se esperaba.

Intenta darle diferentes rotaciones como transform: rotateX(30deg) rotateY(30deg) para jugar con ella poco. Una vez hecho esto, retire el transform propiedad.

Agregar interactividad

Ahora agregamos algunos controles para navegar por la galería. Para esto vamos a usar un buen truco llamado Truco de casilla de verificación. Aunque usaremos botones de radio (ya que solo se seleccionará uno a la vez) en lugar de casillas de verificación, el concepto sigue siendo el mismo. Puede leer más sobre Checkbox Hack en el artículo de Chris Coyier.

Sin profundizar mucho, agregamos los botones de radio a nuestro HTML:

<!-- CONTROLS -->      
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-back" name="select-face"/>
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>

y siguiendo CSS para vincular la rotación del cubo con los botones de opción:

#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg);
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg);
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg);
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg);
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg);
}

En el CSS anterior, simplemente indicamos cuando se marca cada botón de opción, cuál debería ser la rotación del cubo en ese momento.

Código final

Para hacerlo más agradable, agregamos un efecto de transición al cubo y una alineación adecuada para obtener el siguiente código:

<!-- CONTROLS -->
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<input type="radio" id="radio-back" name="select-face"/>

<div></div><!-- separator -->

<div class="scene">
  <div class="cube">
      <div class="cube-face  cube-face-front"></div>
      <div class="cube-face  cube-face-back"></div>
      <div class="cube-face  cube-face-left"></div>
      <div class="cube-face  cube-face-right"></div>
      <div class="cube-face  cube-face-top"></div>
      <div class="cube-face  cube-face-bottom"></div>
   </div>
</div>
$size: 150px; // cube length
body {
  text-align: center;
  padding: 50px;
} 
.scene {
  display: inline-block;
  margin-top: 50px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transition: transform 0.6s;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
// faces
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
}  
.cube-face-back {
  background: black;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}  
// controls 
#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg); 
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg); 
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg); 
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg); 
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg); 
}

agregando algunos background-image a todas las caras obtenemos el resultado final:

Vea la galería de imágenes del cubo Pen 3D de Kushagra Gour (@chinchang) en CodePen

¡Espero que hayas disfrutado de este viaje en 3D y que hayas creado cosas realmente asombrosas usándolo!