Cómo hacer un paquete 3D de CSS puro Trucos de CSS - Aprender Marketing
CSS

Cómo hacer un paquete 3D de CSS puro Trucos de CSS

¿Sabes cómo conseguir una caja de cartón perfectamente plana? Dóblalos y pégalos para hacer una caja útil. Luego, cuando sea el momento de reciclarlos, córtalos para nivelarlos. Recientemente me contactaron para decir que el concepto es esencialmente animación 3D y decidí que sería un tutorial divertido hacerlo completamente en CSS, ¡así que aquí vamos!

¿Cómo se ve esta animación? ¿Cómo crear un calendario de embalaje? ¿Puede el tamaño ser flexible? Hagamos un cambio de CSS limpio en el paquete.

Esto es en lo que estamos trabajando. Haga clic para envolver y abrir la caja de cartón.

¿Donde empezar?

¿Por dónde empiezas algo así? Lo mejor es planificar con anticipación. Sabemos que proporcionaremos una plantilla para nuestro paquete. Esto debe ser doblado en tres dimensiones. Si trabajar con 3D en CSS es nuevo para usted, le recomiendo este artículo para comenzar.

Si está familiarizado con 3D CSS, puede tener la tentación de crear un cuadro y seguir adelante. Sin embargo, esto crea algunos problemas. Necesitamos considerar cómo un paquete cambia de 2D a 3D.

Comencemos por crear una plantilla. Necesitamos planificar nuestro etiquetado con anticipación y pensar en cómo queremos que funcione nuestra animación de shell. Comencemos con un poco de HTML.

<div class="scene">
  <div class="package__wrapper">
    <div class="package">
      <div class="package__side package__side--main">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--tabbed">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
        <div class="package__side package__side--extra">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
          <div class="package__side package__side--flipped">
            <div class="package__flap package__flap--top"></div>
            <div class="package__flap package__flap--bottom"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Los mixins son una gran idea.

Están sucediendo muchas cosas allí. Eso es mucho. divA menudo me gusta usar Pug para generar etiquetas para poder dividir el contenido en piezas reutilizables. Por ejemplo, hay dos válvulas en cada lado. Podemos crear una mezcla de Pug en el costado y usar propiedades para aplicar modificadores de nombre de clase para hacer que todas estas marcas sean más fáciles de escribir.

mixin flaps()
  .package__flap.package__flap--top
  .package__flap.package__flap--bottom
      
mixin side()
  .package__side(class=`package__side--${attributes.class || 'side'}`)
    +flaps()
    if block
      block

.scene
  .package__wrapper
    .package
      +side()(class="main")
        +side()(class="tabbed")
        +side()(class="extra")
          +side()(class="flipped")

Usamos dos mixins. Una persona crea solapas para cada lado de la caja. Otro crea los lados de la caja. side la mezcla que usamos blockAquí es donde se representan los nodos secundarios que usan el mixin, lo cual es especialmente útil ya que necesitamos insertar algunos bordes para facilitarnos la vida más adelante.

Nuestra marca generada:

<div class="scene">
  <div class="package__wrapper">
    <div class="package">
      <div class="package__side package__side--main">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--tabbed">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
        <div class="package__side package__side--extra">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
          <div class="package__side package__side--flipped">
            <div class="package__flap package__flap--top"></div>
            <div class="package__flap package__flap--bottom"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

país anidado

Los lados insertados hacen que nuestros paquetes sean más fáciles de plegar. Es como si tuviera dos solapas en cada lado. Los hijos de borde pueden heredar la transformación de borde y luego aplicar su propia transformación. Si partimos de un paralelepípedo, es difícil sacarle partido.

Vea esta demostración de cambio entre elementos anidados y no anidados para ver la diferencia en el rendimiento.

cada caja tiene uno transform-origin poner abajo a la derecha 100% 100%Marque el interruptor Transformar para rotar cada campo 90degPero mira como se comporta transform Los cambios ocurren si insertamos elementos.

Cambiamos entre las dos versiones de etiquetado, pero no cambiamos nada más.

Adjunto:

<div class="boxes boxes--nested">
  <div class="box">
    <div class="box">
      <div class="box">
        <div class="box"></div>
      </div>
    </div>
  </div>
</div>

no invertido:

<div class="boxes">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
</div>

cambia todo

Una vez que aplicamos algunos estilos a nuestro HTML, tenemos la plantilla del paquete.

Los estilos definen diferentes colores y posicionan los lados del paquete. Cada país tiene una posición en el país «principal». (Verá inmediatamente por qué todos los anidamientos son útiles).

Hay algunas cosas que debes saber. Al igual que con los cuboides, usamos --height, --width, y --depth variable de tamaño. Esto hará que sea más fácil cambiar el tamaño del paquete.

.package {
  height: calc(var(--height, 20) * 1vmin);
  width: calc(var(--width, 20) * 1vmin);
}

¿Por qué dimensionar de esta manera? Usamos el tamaño predeterminado sin unidades 20, una idea que obtuve de Lea Verou Charla de ConfAsia CSS 2016 (Empieza con 52:44). Usando propiedades personalizadas como «datos» en lugar de «valores», somos libres de usarlos para hacer lo que queramos calc()Además, JavaScript no debería estar interesado en unidades de valor, podemos cambiar píxeles, porcentajes, etc. sin cambiar en otro lugar. --rootpero también puede convertirse rápidamente en demasiado.

Las aletas de cada lado también deben ser mucho más pequeñas que el lado en el que están. De esta forma podemos ver huecos sutiles, como en la vida real. Además, las solapas laterales deben quedar un poco más bajas, de esa manera, cuando las plegamos, no nos quedamos z-index batalla entre ellos.

.package__flap {
  width: 99.5%;
  height: 49.5%;
  background: var(--flap-bg, var(--face-4));
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
}
.package__flap--top {
  transform-origin: 50% 100%;
  bottom: 100%;
}
.package__flap--bottom {
  top: 100%;
  transform-origin: 50% 0%;
}
.package__side--extra > .package__flap--bottom,
.package__side--tabbed > .package__flap--bottom {
  top: 99%;
}
.package__side--extra > .package__flap--top,
.package__side--tabbed > .package__flap--top {
  bottom: 99%;
}

Nosotros también empezamos a pensar transform-origin para trabajos individuales. La cubierta superior girará desde su borde inferior y la cubierta inferior girará desde su borde superior.

Podemos usar pseudo-elementos para las pestañas de la derecha clip-path obtener la forma deseada.

.package__side--tabbed:after {
  content: '';
  position: absolute;
  left: 99.5%;
  height: 100%;
  width: 10%;
  background: var(--face-3);
  -webkit-clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  transform-origin: 0% 50%;
}

Comencemos a usar nuestra plantilla en el plano 3D. Podemos rotar desde .scene a lo largo de los ejes X e Y.

.scene {
  transform: rotateX(-24deg) rotateY(-32deg) rotateX(90deg);
}

te encoges

¡Estamos listos para comenzar a reducir nuestra plantilla! Nuestra plantilla se contraerá en función de los atributos personalizados, --packaged. si el valor es 1, y luego podemos reducir la plantilla. Por ejemplo, reduzcamos algunas secciones laterales y de pseudoelementos.

.package__side--tabbed,
.package__side--tabbed:after {
  transform: rotateY(calc(var(--packaged, 0) * -90deg)); 
}
.package__side--extra {
  transform: rotateY(calc(var(--packaged, 0) * 90deg));
}

Alternativamente, podemos escribir reglas para todos los aspectos que no son «primarios».

.package__side:not(.package__side--main),
.package__side:not(.package__side--main):after {
  transform: rotateY(calc((var(--packaged, 0) * var(--rotation, 90)) * 1deg));
}
.package__side--tabbed { --rotation: -90; }

Esto cubrirá todos los aspectos.

¿Recuerdas que dije que los bordes incrustados nos permiten heredar la transformación de los padres? Si actualizamos nuestra demostración para que podamos cambiar --packaged, podemos ver cómo este valor afecta la transformación. Intenta arrastrar --packaged valor entre 1 y 0 Verás lo que quiero decir.

Ahora que tenemos una forma de cambiar el estado colapsado de la plantilla, podemos comenzar a procesar algunas acciones. Nuestra demostración anterior cambia entre dos estados. Podemos usar transition Por lo tanto. La forma más rápida: agregue uno transition ellos llegan transform cada niño en .scene.

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s);
}

¡Transición de múltiples etapas!

Pero no doblamos la plantilla de inmediato; en la vida real hay una secuencia en la que doblamos un lado y sus solapas, luego pasamos al siguiente, y así sucesivamente. Las propiedades de rango personalizadas son excelentes para esto.

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step, 1) * var(--delay, 0.2)) * 1s);
}

Lo que estamos diciendo aquí es para todos. transitionusando un transition-delay sobre --step multiplicar por --delay. Esta --delay El valor no cambia, pero cada elemento puede definir qué «paso» está en la secuencia. Entonces podemos conocer explícitamente el orden en que sucedieron las cosas.

.package__side--extra {
  --step: 1;
}
.package__side--tabbed {
  --step: 2;
}
.package__side--flipped,
.package__side--tabbed::after {
  --step: 3;
}

Considere la siguiente demostración para comprender mejor cómo funciona. Cambie el valor del control deslizante para actualizar el orden en que suceden las cosas. ¿Puedes cambiar qué auto gana?

La misma tecnología es la clave de lo que queremos lograr, incluso podemos imaginar un --initial-delay Esto agrega una pequeña pausa a todo para una sensación más auténtica.

.race__light--animated,
.race__light--animated:after,
.car {
  animation-delay: calc((var(--step, 0) * var(--delay-step, 0)) * 1s);
}

Si revisamos nuestro paquete, podemos ir más allá y aplicar un «paso» a todos los elementos que se implementarán transformEs muy prolijo, pero funciona. Alternativamente, puede insertar estos valores en la selección.

.package__side--extra > .package__flap--bottom {
  --step: 4;
}
.package__side--tabbed > .package__flap--bottom {
  --step: 5;
}
.package__side--main > .package__flap--bottom {
  --step: 6;
}
.package__side--flipped > .package__flap--bottom {
  --step: 7;
}
.package__side--extra > .package__flap--top {
  --step: 8;
}
.package__side--tabbed > .package__flap--top {
  --step: 9;
}
.package__side--main > .package__flap--top {
  --step: 10;
}
.package__side--flipped > .package__flap--top {
  --step: 11;
}

Sin embargo, no se siente realista.

Tal vez deberíamos darle la vuelta a la caja

Si doblara la caja en la vida real, probablemente le daría la vuelta antes de doblar la tapa superior. Entonces, ¿qué debemos hacer? Bueno, aquellos con ojos agudos pueden haber notado .package__wrapper elemento. Usaremos esto para deslizar el caparazón. Luego rotaremos la bolsa en el eje x. Esto dará la impresión de voltear el paquete hacia un lado.

.package {
  transform-origin: 50% 100%;
  transform: rotateX(calc(var(--packaged, 0) * -90deg));
}
.package__wrapper {
  transform: translate(0, calc(var(--packaged, 0) * -100%));
}

ajustar --step En consecuencia, la declaración nos da algo similar.

expandir la caja

Si cambia entre contraída y desplegada, notará que la extensión no se ve bien. El orden de despliegue debe ser exactamente opuesto al orden de plegado. --step Residencia en --packaged y número de pasos Nuestros últimos pasos son 15Podemos actualizar el nuestro. transition Ven aquí:

.scene *,
.scene *:after {
  --no-of-steps: 15;
  --step-delay: calc(var(--step, 1) - ((1 - var(--packaged, 0)) * (var(--step) - ((var(--no-of-steps) + 1) - var(--step)))));
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step-delay) * var(--delay, 0.2)) * 1s);
}

sabe bastante bien calc retortijón transition-delay¡Sin embargo, funciona! Debemos recordarnos a nosotros mismos mantener este --no-of-steps ¡El valor está actualizado!

Tenemos otra opción. A medida que continuamos por la ruta de «CSS puro», eventualmente usaremos Hackear en la casilla de verificación Cambiar entre estados colapsados. Podemos tener dos conjuntos de «pasos» definidos, uno de los cuales está activo cuando está marcado en nuestra casilla. Esta es, por supuesto, una solución más detallada. Sin embargo, esto nos da un control más limitado.

/* Folding */
:checked ~ .scene .package__side--extra {
  --step: 1;
}
/* Unfolding */
.package__side--extra {
  --step: 15;
}

Redimensionar y centrar

antes de que nos rindamos [dat.gui](https://github.com/dataarts/dat.gui) En nuestra demostración, juguemos con el tamaño de nuestra bolsa. Queremos comprobar si nuestro paquete se mantiene centrado al doblar y girar. En esta demostración, el paquete tiene una mayor --height y .scene Hay un borde punteado.

Ajustemos el nuestro transform Mejor centrar el paquete al usarlo:

/* Considers package height by translating on z-axis */
.scene {
  transform: rotateX(calc(var(--rotate-x, -24) * 1deg)) rotateY(calc(var(--rotate-y, -32) * 1deg)) rotateX(90deg) translate3d(0, 0, calc(var(--height, 20) * -0.5vmin));
}
/* Considers package depth by sliding the depth before flipping */
.package__wrapper {
  transform: translate(0, calc((var(--packaged, 0) * var(--depth, 20)) * -1vmin));
}

Esto nos permite centrarnos de forma fiable en la escena. ¡Todo se reduce a las preferencias!

Agregar un truco a la casilla de verificación

Ahora vamos a tomarlo dat.gui Haz este CSS «puro». Para hacer esto, necesitamos introducir un montón de controles en HTML. Usaremos una casilla de verificación para colapsar y expandir nuestro paquete. usamos un radio botón para seleccionar el tamaño del paquete.

<input id="package" type="checkbox"/>

<input id="one" type="radio" name="size"/>
<label class="size-label one" for="one">S</label>

<input id="two" type="radio" name="size" checked="checked"/>
<label class="size-label two" for="two">M</label>

<input id="three" type="radio" name="size"/>
<label class="size-label three" for="three">L</label>

<input id="four" type="radio" name="size"/>
<label class="size-label four" for="four">XL</label>

<label class="close" for="package">Close Package</label>
<label class="open" for="package">Open Package</label>

En la última demostración, ocultaremos la entrada y usaremos el elemento de etiqueta. Por ahora, sin embargo, hagámoslos visibles. El truco es usar el combinador hermano y hermana (~) cuando algunos controles reciben :checkedEntonces podemos .scene.

#package:checked ~ .scene {
  --packaged: 1;
}
#one:checked ~ .scene {
  --height: 10;
  --width: 20;
  --depth: 20;
}
#two:checked ~ .scene {
  --height: 20;
  --width: 20;
  --depth: 20;
}
#three:checked ~ .scene {
  --height: 20;
  --width: 30;
  --depth: 20;
}
#four:checked ~ .scene {
  --height: 30;
  --width: 20;
  --depth: 30;
}

¡Aquí hay una demostración del trabajo!

pulido final

Ahora podemos hacer que las cosas se vean «hermosas» y agregar algunos toques adicionales. Comencemos ocultando todas las entradas.

input {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Podemos configurar la opción para el tamaño del botón redondo:

.size-label {
  position: fixed;
  top: var(--top);
  right: 1rem;
  z-index: 3;
  font-family: sans-serif;
  font-weight: bold;
  color: #262626;
  height: 44px;
  width: 44px;
  display: grid;
  place-items: center;
  background: #fcfcfc;
  border-radius: 50%;
  cursor: pointer;
  border: 4px solid #8bb1b1;
  transform: translate(0, calc(var(--y, 0) * 1%)) scale(var(--scale, 1));
  transition: transform 0.1s;
}
.size-label:hover {
  --y: -5;
}
.size-label:active {
  --y: 2;
  --scale: 0.9;
}

Queremos poder hacer clic en cualquier lugar para cambiar entre paquetes colapsados ​​y extendidos. .open y .close La sección ocupará toda la pantalla. ¿Se pregunta por qué tenemos dos secciones? Este es un pequeño truco, si usamos un transition-delay Y al agrandar las etiquetas apropiadas, podemos ocultar ambas etiquetas cuando se transfiere el paquete. Aquí se explica cómo combatir las escuchas de spam (incluso si no impide que los usuarios presionen la barra espaciadora de su teclado).

.close,
.open {
  position: fixed;
  height: 100vh;
  width: 100vw;
  z-index: 2;
  transform: scale(var(--scale, 1)) translate3d(0, 0, 50vmin);
  transition: transform 0s var(--reveal-delay, calc(((var(--no-of-steps, 15) + 1) * var(--delay, 0.2)) * 1s));
}

#package:checked ~ .close,
.open {
  --scale: 0;
  --reveal-delay: 0s;
}
#package:checked ~ .open {
  --scale: 1;
  --reveal-delay: calc(((var(--no-of-steps, 15) + 1) * var(--delay, 0.2)) * 1s);
}

Mira esta demostración para ver dónde hemos agregado background-color ambos .open y .closeAmbas etiquetas son invisibles durante la transición.

¡Tenemos funcionalidad completa! Sin embargo, nuestro paquete es un poco impresionante en este momento. Agreguemos más detalles para hacer cosas como la cinta de embalaje y las etiquetas de embalaje más como «cajas».

¡Pequeños detalles como este están limitados solo por nuestra imaginación! --packaged Las propiedades personalizadas afectan todo. Por ejemplo, .package__tape transición scaleY cambio:

.package__tape {
  transform: translate3d(-50%, var(--offset-y), -2px) scaleX(var(--packaged, 0));
}

Lo que debe recordar es que debemos actualizar nuestros pasos cada vez que agregamos nuevas funciones que afectan la secuencia. --step valores, pero también --no-of-steps valor.

¡Está!

Aquí se explica cómo cambiar a un paquete 3D CSS puro. ¿Lo pondrás en tu sitio web? ¡No es muy posible! Pero es interesante ver cómo puedes lograr estas cosas con CSS. Las propiedades personalizadas son muy poderosas.

¡Por qué no ser súper festivo y regalar CSS!

¡Sigue adelante! ʕ • ᴥ • ʔ

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

Botón volver arriba