CSS

Crea el logo de DigitalOcean en 3D con CSS trucos CSS

¡Hola! A menos que haya vivido debajo de una roca (y tal vez incluso entonces), sin duda ha escuchado la noticia de que DigitalOcean ha adquirido CSS-Tricks. ¡Felicitaciones a todos! 🥳

Con un poco de alegría para conmemorar la ocasión, quería crear el logotipo de DigitalOcean en CSS. Lo hice, pero luego lo hice un poco más con un poco de 3D y paralaje. Esto también lo convierte en un artículo bastante bueno, porque la forma en que hice el logotipo utiliza diferentes partes de artículos anteriores que he escrito. Esta pequeña gran demostración reúne muchos de estos conceptos.

Entonces, ¡vamos a sumergirnos ahora!

Creación del logotipo de DigitalOcean

«Rastrearemos» el logotipo de DigitalOcean tomando una versión SVG del mismo. iconossimples.org.

<svg role="img" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <title>DigitalOcean</title>
  <path d="M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z"></path>
</svg>

Dado que tomamos este 3D, podemos envolver nuestro SVG en un .scene Luego podemos usar la técnica de seguimiento de mi artículo, Consejos para ilustraciones CSS avanzadas. doguillo para que podamos usar sus mezclas y reducir la cantidad de marcas que tenemos que escribir para la parte 3D.

- const SIZE = 40
.scene
  svg(role="img" viewbox='0 0 24 24' xmlns="http://www.w3.org/2000/svg")
    title DigitalOcean
    path(d='M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z')
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
    .logo__square.logo__square--two
    .logo__square.logo__square--three

La idea es diseñar estos elementos para que se superpongan a nuestro logotipo. No necesitamos crear la parte del «arco» del logotipo, porque estamos pensando en el futuro, porque haremos este logotipo en 3D y podemos crear el arco con dos formas cilíndricas. Esto significa que, por ahora, todo lo que necesitamos es los elementos contenedores de cada cilindro, el arco interior y el arco exterior.

Vea esta demostración que muestra las diferentes partes del logotipo de DigitalOcean. Si cambia «Explotar» y mantiene presionado el cursor del mouse, puede ver en qué consiste el logotipo.

Si queremos un logotipo plano de DigitalOcean, podemos usar una máscara CSS con un degradado cónico. Entonces solo necesitaremos un elemento de «arco» que use un borde sólido.

.logo__arc--outer {
  border: calc(var(--size) * 0.1925vmin) solid #006aff;
  mask: conic-gradient(transparent 0deg 90deg, #000 90deg);
  transform: translate(-50%, -50%) rotate(180deg);
}

Esto nos dará el logo. Transiciones para «divulgación» a clip-path que muestra la imagen SVG rastreada a continuación.

Consulte mi artículo «Sugerencias para ilustraciones CSS complejas» para obtener sugerencias sobre cómo trabajar con ilustraciones CSS avanzadas.

Extrusión para 3D

Tenemos el dibujo de nuestro logo de DigitalOcean, así que es hora de hacer esto en 3D. ¿Por qué no creamos bloques 3D desde el principio? La creación de elementos contenedores facilita la creación de 3D por extrusión.

Analizamos la creación de escenas 3D en CSS en mi artículo «Aprender a pensar en cubos en lugar de cajas». Usaremos algunas de estas técnicas para lo que hacemos aquí. Comencemos con los cuadrados en el logo. Cada cuadrado es Y usa Pug, crearemos y usaremos un cuboid mixin para ayudar a generarlos todos.

mixin cuboid()
  .cuboid(class!=attributes.class)
    if block
      block
    - let s = 0
    while s < 6
      .cuboid__side
      - s++

Entonces podemos usar esto en nuestra marca:

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three

Luego necesitamos los estilos para mostrar nuestros cuboides. Tenga en cuenta que los cuboides tienen seis lados, por lo que los estilizamos con nth-of-type() pseudo selector durante el uso vmin unidad de longitud para mantener las cosas receptivas.

.cuboid {
  width: 100%;
  height: 100%;
  position: relative;
}
.cuboid__side {
  filter: brightness(var(--b, 1));
  position: absolute;
}
.cuboid__side:nth-of-type(1) {
  --b: 1.1;
  height: calc(var(--depth, 20) * 1vmin);
  width: 100%;
  top: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(2) {
  --b: 0.9;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  top: 50%;
  right: 0;
  transform: translate(50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(3) {
  --b: 0.5;
  width: 100%;
  height: calc(var(--depth, 20) * 1vmin);
  bottom: 0;
  transform: translate(0%, 50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(4) {
  --b: 1;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  left: 0;
  top: 50%;
  transform: translate(-50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(5) {
  --b: 0.8;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * 0.5vmin));
  top: 0;
  left: 0;
}
.cuboid__side:nth-of-type(6) {
  --b: 1.2;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * -0.5vmin)) rotateY(180deg);
  top: 0;
  left: 0;
}

Abordamos esto de manera diferente a como lo hicimos en artículos anteriores. En lugar de aplicar altura, ancho y profundidad a un paralelepípedo, solo nos interesa su profundidad. Y en lugar de intentar colorear cada lado, podemos aprovechar filter: brightness para lidiar con eso por nosotros.

Si necesita tener cuboides u otros elementos 3D como un niño en el costado usando filterpuede que tenga que mezclar las cosas. El lado filtrado igualará a todos los niños 3D.

El logo de DigitalOcean tiene tres cubos, así que tenemos una clase para cada uno y les damos forma así:

.square-cuboid .cuboid__side {
  background: hsl(var(--hue), 100%, 50%);
}
.square-cuboid--one {
  /* 0.1925? It's a percentage of the --size for that square */
  --depth: calc((var(--size) * 0.1925) * var(--depth-multiplier));
}
.square-cuboid--two {
  --depth: calc((var(--size) * 0.1475) * var(--depth-multiplier));
}
.square-cuboid--three {
  --depth: calc((var(--size) * 0.125) * var(--depth-multiplier));
}

… lo que nos da algo como esto:

¡Puedes jugar con el control deslizante de profundidad para extruir los cuboides como desees! Para nuestra demostración, elegimos hacer que los paralelepípedos fueran cubos reales de la misma altura, anchura y profundidad. La profundidad del arco corresponderá al paralelepípedo más grande.

Ahora para los cilindros la idea es crear dos extremos que usen border-radius: 50%Entonces podemos usar muchos elementos como los lados del cilindro para crear un efecto. El truco está en posicionar todos los lados.

Hay varios enfoques que podemos tomar para crear cilindros CSS. Pero para mí, si esto es algo que puedo anticipar usar muchas veces, intentaré mencionarlo en el futuro. Esto significa hacer mixin y algunos estilos que puedo reutilizar para otras demostraciones. Y estos estilos deberían tratar de ocuparse de los escenarios que podría ver aparecer. Hay alguna configuración para un cilindro que tal vez queramos considerar:

  • radio
  • los paises
  • cuantos de estos paises se muestran
  • si mostrar uno o ambos extremos del cilindro

Juntando esto, podemos crear una mezcla de pug que satisfaga estas necesidades:

mixin cylinder(radius = 10, sides = 10, cut = [5, 10], top = true, bottom = true)
  - const innerAngle = (((sides - 2) * 180) / sides) * 0.5
  - const cosAngle = Math.cos(innerAngle * (Math.PI / 180))
  - const side =  2 * radius * Math.cos(innerAngle * (Math.PI / 180))
  //- Use the cut to determine how many sides get rendered and from what point
  .cylinder(style=`--side: ${side}; --sides: ${sides}; --radius: ${radius};` class!=attributes.class)
    if top
      .cylinder__end.cylinder__segment.cylinder__end--top
    if bottom
      .cylinder__end.cylinder__segment.cylinder__end--bottom
    - const [start, end] = cut
    - let i = start
    while i < end
      .cylinder__side.cylinder__segment(style=`--index: ${i};`)
      - i++

Ver cómo //- está al principio del comentario en el código? Esto le dice a Pug que ignore el comentario. y déjelo fuera del marcado HTML compilado.

¿Por qué tenemos que transmitir el radio en el cilindro? Bueno, desafortunadamente, no podemos manejar la trigonometría con CSS. calc() todavía (pero está viniendo). Y necesitamos hacer cosas como el ancho de los lados del cilindro y qué tan lejos del centro debemos diseñar. Lo bueno es que tenemos una buena manera de transmitir esta información a nuestros estilos a través de propiedades personalizadas integradas.

.cylinder(
  style=`
    --side: ${side};
    --sides: ${sides};
    --radius: ${radius};`
  class!=attributes.class
)

Un ejemplo del uso de nuestro mixin sería el siguiente:

+cylinder(20, 30, [10, 30])

Esto creará un cilindro con un radio de 20, 30 países donde sólo países 10 para 30 son presentados.

Entonces necesitamos un poco de estilización. Afortunadamente, diseñar cilindros para el logotipo de DigitalOcean es bastante fácil:

.cylinder {
  --bg: hsl(var(--hue), 100%, 50%);
  background: rgba(255,43,0,0.5);
  height: 100%;
  width: 100%;
  position: relative;
}
.cylinder__segment {
  filter: brightness(var(--b, 1));
  background: var(--bg, #e61919);
  position: absolute;
  top: 50%;
  left: 50%;
}
.cylinder__end {
  --b: 1.2;
  --end-coefficient: 0.5;
  height: 100%;
  width: 100%;
  border-radius: 50%;
  transform: translate3d(-50%, -50%, calc((var(--depth, 0) * var(--end-coefficient)) * 1vmin));
}
.cylinder__end--bottom {
  --b: 0.8;
  --end-coefficient: -0.5;
}
.cylinder__side {
  --b: 0.9;
  height: calc(var(--depth, 30) * 1vmin);
  width: calc(var(--side) * 1vmin);
  transform: translate(-50%, -50%) rotateX(90deg) rotateY(calc((var(--index, 0) * 360 / var(--sides)) * 1deg)) translate3d(50%, 0, calc(var(--radius) * 1vmin));
}

La idea es crear todos los lados del cilindro y colocarlos en el medio del cilindro, luego rotarlos a lo largo del eje Y y proyectarlos hacia afuera aproximadamente a la distancia del radio.

No es necesario mostrar los extremos del cilindro por dentro, ya que ya están oscurecidos. Pero tenemos que mostrarlos por fuera. El uso de una mezcla de dos cilindros se ve así:

.logo(style=`--size: ${SIZE}`)
  .logo__arc.logo__arc--inner
    +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
  .logo__arc.logo__arc--outer
    +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer

Conocemos el radio del diámetro que usamos para rastrear el logotipo anteriormente. Además, podemos usar los bordes exteriores del cilindro para crear las caras del logotipo de DigitalOcean. Una combinación de border-width y clip-path es útil aquí.

.cylinder-arc--outer .cylinder__end--top,
.cylinder-arc--outer .cylinder__end--bottom {
  /* Based on the percentage of the size needed to cap the arc */
  border-width: calc(var(--size) * 0.1975vmin);
  border-style: solid;
  border-color: hsl(var(--hue), 100%, 50%);
  --clip: polygon(50% 0, 50% 50%, 0 50%, 0 100%, 100% 100%, 100% 0);
  clip-path: var(--clip);
}

¡Estamos bastante cerca de donde queremos estar!

Sin embargo, falta una cosa: cerrar el arcoíris. Necesitamos crear algunos extremos del arco, lo que requiere dos elementos que podemos posicionar y rotar en el eje X o Y:

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
      +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
    .logo__arc.logo__arc--outer
      +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three
    .logo__cap.logo__cap--top
    .logo__cap.logo__cap--bottom

Los extremos limitados del arco tomarán la altura y el ancho en función del extremo. border-width valor, así como la profundidad del arco.

.logo__cap {
  --hue: 10;
  position: absolute;
  height: calc(var(--size) * 0.1925vmin);
  width: calc(var(--size) * 0.1975vmin);
  background: hsl(var(--hue), 100%, 50%);
}
.logo__cap--top {
  top: 50%;
  left: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.logo__cap--bottom {
  bottom: 0;
  right: 50%;
  transform: translate(50%, 0) rotateY(90deg);
  height: calc(var(--size) * 0.1975vmin);
  width: calc(var(--size) * 0.1925vmin);
}

¡Cerramos el arcoíris!

Poniéndolo todo junto, tenemos nuestro logotipo de DigitalOcean. Esta demostración le permite girarlo en diferentes direcciones.

¡Pero todavía hay otro truco bajo la manga!

Agregar efecto de paralaje al logo

Tenemos nuestro logo 3D DigitalOcean, pero sería bueno si fuera interactivo de alguna manera. Ya en noviembre de 2021, analizamos cómo crear un efecto de paralaje con propiedades CSS personalizadas. Usemos la misma técnica aquí, la idea es que el logotipo gire y se mueva siguiendo el cursor del mouse del usuario.

Necesitamos una pizca de JavaScript para poder actualizar las propiedades personalizadas que necesitamos para un factor que establece el movimiento del logotipo en los ejes X e Y en CSS. Estos coeficientes se calculan a partir de la posición del puntero del usuario. a menudo usaré Calcetín Verde para que pueda usar gsap.utils.mapRangePero aquí hay una versión de JavaScript que aplica. mapRange:

const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => {
  const INPUT_RANGE = inputUpper - inputLower
  const OUTPUT_RANGE = outputUpper - outputLower
  return value => outputLower + (((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0)
}

const BOUNDS = 100      
const update = ({ x, y }) => {
  const POS_X = mapRange(0, window.innerWidth, -BOUNDS, BOUNDS)(x)
  const POS_Y = mapRange(0, window.innerHeight, -BOUNDS, BOUNDS)(y)
  document.body.style.setProperty('--coefficient-x', POS_X)
  document.body.style.setProperty('--coefficient-y', POS_Y)
}

document.addEventListener('pointermove', update)

La magia sucede en CSS-Earth. Esta es una de las principales ventajas de usar propiedades personalizadas de esta manera. JavaScript le dice a CSS lo que sucede con la interacción. Pero no le importa lo que CSS haga con él. Esta es una división radical. Utilizo este fragmento de JavaScript en muchas de mis demostraciones por este mismo motivo. Podemos crear diferentes experiencias simplemente actualizando CSS.

¿Como hacer esto? Usar calc() y propiedades personalizadas que van directamente desde .scene elemento. Piense en estos estilos actualizados para .scene:

.scene {
  --rotation-y: 75deg;
  --rotation-x: -14deg;
  transform: translate3d(0, 0, 100vmin)
    rotateX(-16deg)
    rotateY(28deg)
    rotateX(calc(var(--coefficient-y, 0) * var(--rotation-x, 0deg)))
    rotateY(calc(var(--coefficient-x, 0) * var(--rotation-y, 0deg)));
}

Esto hace que la escena gire sobre los ejes X e Y según el movimiento del puntero del usuario. Pero podemos corregir este comportamiento estableciendo los valores para --rotation-x y --rotation-y.

Cada cuboide se moverá a su manera. Pueden moverse a lo largo del eje X, Y o Z. Pero solo necesitamos definir uno transformLuego podemos usar propiedades de alcance personalizadas para hacer el resto.

.logo__square {
  transform: translate3d(
    calc(min(0, var(--coefficient-x, 0) * var(--offset-x, 0)) * 1%),
    calc((var(--coefficient-y) * var(--offset-y, 0)) * 1%),
    calc((var(--coefficient-x) * var(--offset-z, 0)) * 1vmin)
  );
}
.logo__square--one {
  --offset-x: 50;
  --offset-y: 10;
  --offset-z: -2;
}
.logo__square--two {
  --offset-x: -35;
  --offset-y: -20;
  --offset-z: 4;
}
.logo__square--three {
  --offset-x: 25;
  --offset-y: 30;
  --offset-z: -6;
}

Esto te dará algo como esto:

¡Y podemos hacerlos felices hasta que obtengamos algo con lo que estemos felices!

Agregue una animación introductoria a la mezcla.

Bien, lo he pensado un poco y tengo un final (¡lo prometo!) Una forma en que podemos mejorar nuestro trabajo. ¿Y si tuviéramos algún tipo de animación de introducción? ¿Qué tal lana o algo que se derrame y revele el logo?

Podríamos hacer esto con los pseudo-elementos de body elemento:

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
}

body:after,
body:before {
  content: '';
  position: absolute;
  height: 100vh;
  width: 100vw;
  background: hsl(var(--hue), 100%, calc(var(--lightness, 50) * 1%));
  transform: translate(100%, 0);
  animation-name: wave;
  animation-duration: calc(var(--wave-speed) * 1s);
  animation-delay: calc(var(--initial-delay) * 1s);
  animation-timing-function: ease-in;
}
body:before {
  --lightness: 85;
  animation-timing-function: ease-out;
}
@keyframes wave {
  from {
    transform: translate(-100%, 0);
  }
}

Ahora la idea es mantener oculto el logo de DigitalOcean hasta que la ola lo borre. Para este efecto animaremos nuestros elementos 3D from opacidad de 0Y animaremos todos los lados de nuestros elementos 3D desde un brightness sobre 1 para revelar el logotipo. Como el color de la lana coincide con el color del logo, no lo veremos desvanecerse. Además, usando animation-fill-mode: both significa que nuestros elementos expandirán el estilo de nuestros fotogramas clave en ambas direcciones.

Esto requiere algún tipo de línea de tiempo de animación. Y aquí entran en juego las propiedades personalizadas. Podemos usar la duración de nuestras animaciones para calcular los retrasos de otras. Vimos esto en mi Cómo cambiar a un paquete 3D de CSS puro y artículos en CSS Animated Matryoshka Dolls.

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
  --fade-speed: 0.5;
  --filter-speed: 1;
}

.cylinder__segment,
.cuboid__side,
.logo__cap {
  animation-name: fade-in, filter-in;
  animation-duration: calc(var(--fade-speed) * 1s),
    calc(var(--filter-speed) * 1s);
  animation-delay: calc((var(--initial-delay) + var(--wave-speed)) * 0.75s),
    calc((var(--initial-delay) + var(--wave-speed)) * 1.15s);
  animation-fill-mode: both;
}

@keyframes filter-in {
  from {
    filter: brightness(1);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

¿Cómo determinar el momento adecuado? Un poco de retoques y el uso de Animation Inspector en Chrome’s DevTool es muy útil. Trate de ajustar el tiempo en este demostración:

Es posible que el tiempo de atenuación no sea necesario si desea que el logotipo permanezca allí después de que pase la ola. En este caso, intente ajustar la atenuación de 0En particular, experimente con filter y fade coeficientes Se refieren a 0.75s y 1.15s del código de arriba. Vale la pena arreglar cosas y jugar Inspector de animación de Chrome para ver cómo funcionan las cosas.

¡Está!

¡Combinando todo esto, tenemos esta elegante introducción para nuestro logotipo 3D DigitalOcean!

Y, por supuesto, este es solo un enfoque para crear el logotipo de DigitalOcean en 3D con CSS. Si ve otras opciones o tal vez algo que pueda optimizarse aún más, publique un enlace a su demostración en los comentarios.

Felicitaciones nuevamente al equipo de CSS-Tricks y DigitalOcean por su nueva asociación. Estoy emocionado de ver a dónde van las cosas con la adquisición. Una cosa es segura: CSS-Tricks continuará inspirando y produciendo contenido fantástico para la comunidad. 😎

Deja una respuesta

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

Botón volver arriba