Cómo compilar un componente del menú del juego en 3D

Descripción general de los conceptos básicos sobre cómo crear un menú de juego en 3D responsivo, adaptable y accesible

En esta publicación, quiero contarte cómo crear un componente del menú de un juego en 3D. Prueba la demostración.

. Demostración

Si prefieres ver un video, aquí tienes una versión de YouTube de esta publicación:

Descripción general

Los videojuegos suelen presentar a los usuarios un menú creativo e inusual, animado y en un espacio 3D. En los juegos de RA y RV, es popular hacer que el menú parezca estar flotando en el espacio. Hoy recrearemos los elementos esenciales de este efecto, con el estilo adicional de un esquema de colores adaptable y adaptaciones para los usuarios que prefieren menos movimiento.

HTML

El menú del juego es una lista de botones. La mejor manera de representar esto en HTML es como sigue:

<ul class="threeD-button-set">
  <li><button>New Game</button></li>
  <li><button>Continue</button></li>
  <li><button>Online</button></li>
  <li><button>Settings</button></li>
  <li><button>Quit</button></li>
</ul>

Una lista de botones se anunciará bien para las tecnologías de lectores de pantalla y funciona sin JavaScript ni CSS.

pañal
lista de viñetas de aspecto muy genérico con botones regulares como elementos.

CSS

El diseño de la lista de botones se divide en los siguientes pasos de alto nivel:

  1. Configurar propiedades personalizadas
  2. Un diseño de flexbox
  3. Un botón personalizado con pseudoelementos decorativos.
  4. Colocación de elementos en un espacio 3D

Descripción general de las propiedades personalizadas

Las propiedades personalizadas ayudan a desambiguar valores, ya que brindan nombres a valores de aspecto aleatorio, lo que evita el uso entre los niños.

A continuación, se muestran las consultas de medios guardadas como variables de CSS, también conocidas como valores personalizados contenido multimedia. Son globales y se usará en varios selectores para que el código sea conciso y legible. El El componente del menú del juego usa movimiento preferencias, color del sistema esquema, y rango de color capacidades de la pantalla.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);

Las siguientes propiedades personalizadas administran el esquema de colores y mantienen presionado el mouse. valores posicionales para que se pueda colocar el cursor sobre el menú del juego de forma interactiva. Asignación de nombres personalizada propiedades ayudan a la legibilidad del código, ya que revela el caso de uso del valor o un nombre descriptivo para el resultado del valor.

.threeD-button-set {
  --y:;
  --x:;
  --distance: 1px;
  --theme: hsl(180 100% 50%);
  --theme-bg: hsl(180 100% 50% / 25%);
  --theme-bg-hover: hsl(180 100% 50% / 40%);
  --theme-text: white;
  --theme-shadow: hsl(180 100% 10% / 25%);

  --_max-rotateY: 10deg;
  --_max-rotateX: 15deg;
  --_btn-bg: var(--theme-bg);
  --_btn-bg-hover: var(--theme-bg-hover);
  --_btn-text: var(--theme-text);
  --_btn-text-shadow: var(--theme-shadow);
  --_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);

  @media (--dark) {
    --theme: hsl(255 53% 50%);
    --theme-bg: hsl(255 53% 71% / 25%);
    --theme-bg-hover: hsl(255 53% 50% / 40%);
    --theme-shadow: hsl(255 53% 10% / 25%);
  }

  @media (--HDcolor) {
    @supports (color: color(display-p3 0 0 0)) {
      --theme: color(display-p3 .4 0 .9);
    }
  }
}

Fondos cónicos del fondo del tema claro y oscuro

El tema claro tiene un tono cónico de cyan a deeppink de colores intensos gradiente mientras que el tema oscuro tiene un gradiente cónico sutil oscuro. Para ver más sobre qué esto se puede hacer con gradientes cónicos, consulta conic.style.

html {
  background: conic-gradient(at -10% 50%, deeppink, cyan);

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Demostración del cambio de fondo entre las preferencias de color claro y oscuro.

Cómo habilitar la perspectiva 3D

Para que los elementos existan en el espacio 3D de una página web, un viewport con perspectiva debe inicializarse. Elegí poner la perspectiva en el elemento body. y usé unidades de viewport para crear el estilo que me gustaba.

body {
  perspective: 40vw;
}

Este es el tipo de perspectiva de impacto que puede tener.

Cómo aplicar diseño a la lista de botones <ul>

Este elemento es responsable del diseño general de la macro de la lista de botones, así como del es una tarjeta interactiva y flotante en 3D. Aquí te mostramos una manera de lograrlo.

Diseño del grupo de botones

Flexbox puede administrar el diseño del contenedor. Cambiar la dirección predeterminada de Flex de filas a columnas con flex-direction y asegúrate de que cada elemento tenga el tamaño de su contenido cambiando de stretch a start para align-items

.threeD-button-set {
  /* remove <ul> margins */
  margin: 0;

  /* vertical rag-right layout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
}

Luego, establece el contenedor como un contexto de espacio 3D y configura el clamp() del CSS. para garantizar que la tarjeta no rote más allá de rotaciones legibles. Aviso que el valor medio de la abrazadera es una propiedad personalizada, estos --x y --y los valores se establecerán desde JavaScript con el mouse interacción más adelante.

.threeD-button-set {
  

  /* create 3D space context */
  transform-style: preserve-3d;

  /* clamped menu rotation to not be too extreme */
  transform:
    rotateY(
      clamp(
        calc(var(--_max-rotateY) * -1),
        var(--y),
        var(--_max-rotateY)
      )
    )
    rotateX(
      clamp(
        calc(var(--_max-rotateX) * -1),
        var(--x),
        var(--_max-rotateX)
      )
    )
  ;
}

Luego, si el movimiento del usuario es aceptable, agrega una sugerencia al navegador de que la transformación de este elemento cambiará constantemente con will-change Además, habilita la interpolación estableciendo un transition en las transformaciones. Esta cuando el mouse interactúe con la tarjeta, lo que permite las transiciones a los cambios de rotación. La animación es una animación de ejecución constante que demuestre el espacio 3D dentro de la tarjeta, incluso si un mouse no puede no está interactuando con el componente.

@media (--motionOK) {
  .threeD-button-set {
    /* browser hint so it can be prepared and optimized */
    will-change: transform;

    /* transition transform style changes and run an infinite animation */
    transition: transform .1s ease;
    animation: rotate-y 5s ease-in-out infinite;
  }
}

La animación rotate-y solo establece el fotograma clave del medio en 50%, ya que el el navegador aplicará 0% y 100% de forma predeterminada al estilo predeterminado del elemento. Esta es una abreviatura de animaciones que se alternan, necesitan comenzar y terminar de la misma manera posición. Es una excelente manera de articular animaciones alternadas infinitas.

@keyframes rotate-y {
  50% {
    transform: rotateY(15deg) rotateX(-6deg);
  }
}

Cómo aplicar diseño a los elementos <li>

Cada elemento de la lista (<li>) contiene el botón y sus elementos de borde. El Se cambió el estilo de display para que el elemento no muestre un ::marker Estilo position se configura en relative para que los próximos seudoelementos del botón puedan posicionarse dentro del área completa que consume el botón.

.threeD-button-set > li {
  /* change display type from list-item */
  display: inline-flex;

  /* create context for button pseudos */
  position: relative;

  /* create 3D space context */
  transform-style: preserve-3d;
}

Captura de pantalla de la lista rotada en un espacio 3D para mostrar la perspectiva
cada elemento de la lista ya no tiene una viñeta.

Cómo aplicar diseño a los elementos <button>

Diseñar los botones puede ser un trabajo difícil, hay muchos estados y tipos de interacción. tener en cuenta. Estos botones se vuelven complejos rápidamente debido al equilibrio seudoelementos, animaciones e interacciones.

<button> diseños iniciales

A continuación, se muestran los estilos básicos que admitirán los otros estados.

.threeD-button-set button {
  /* strip out default button styles */
  appearance: none;
  outline: none;
  border: none;

  /* bring in brand styles via props */
  background-color: var(--_btn-bg);
  color: var(--_btn-text);
  text-shadow: 0 1px 1px var(--_btn-text-shadow);

  /* large text rounded corner and padded*/
  font-size: 5vmin;
  font-family: Audiowide;
  padding-block: .75ch;
  padding-inline: 2ch;
  border-radius: 5px 20px;
}

Captura de pantalla de la lista de botones en perspectiva 3D, esta vez con estilo
botones.

Pseudoelementos de botones

Los bordes del botón no son tradicionales, son una posición absoluta. seudoelementos con bordes.

Captura de pantalla del panel Elementos de herramientas para desarrolladores de Chrome. Se muestra un botón con
elementos ::before y ::after.

Estos elementos son cruciales para mostrar la perspectiva 3D que se ha establecidos. Uno de estos seudoelementos será alejado del botón, y uno se acercará al usuario. El efecto es más evidente en la inferior y superior.

.threeD-button button {
  

  &::after,
  &::before {
    /* create empty element */
    content: '';
    opacity: .8;

    /* cover the parent (button) */
    position: absolute;
    inset: 0;

    /* style the element for border accents */
    border: 1px solid var(--theme);
    border-radius: 5px 20px;
  }

  /* exceptions for one of the pseudo elements */
  /* this will be pushed back (3x) and have a thicker border */
  &::before {
    border-width: 3px;

    /* in dark mode, it glows! */
    @media (--dark) {
      box-shadow:
        0 0 25px var(--theme),
        inset 0 0 25px var(--theme);
    }
  }
}

Estilos de transformación 3D

El valor inferior a transform-style se estableció en preserve-3d para que los elementos secundarios puedan agregar espacios en el eje z. transform se establece en --distance. propiedad personalizada, que aumentará cuando coloque el cursor y enfoque.

.threeD-button-set button {
  

  transform: translateZ(var(--distance));
  transform-style: preserve-3d;

  &::after {
    /* pull forward in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3));
  }

  &::before {
    /* push back in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3 * -1));
  }
}

Estilos de animación condicionales

Si el usuario está de acuerdo con el movimiento, el botón sugiere al navegador que de transformación debe estar lista para el cambio y se debe establecer una transición para Propiedades transform y background-color. Observa la diferencia en me pareció que generaba un bonito efecto escalonado.

.threeD-button-set button {
  

  @media (--motionOK) {
    will-change: transform;
    transition:
      transform .2s ease,
      background-color .5s ease
    ;

    &::before,
    &::after {
      transition: transform .1s ease-out;
    }

    &::after    { transition-duration: .5s }
    &::before { transition-duration: .3s }
  }
}

Estilos de interacción de colocar el cursor sobre un elemento y enfocar

El objetivo de la animación de interacción es extender las capas que componen la botón de aparición plana. Para lograrlo, configura la variable --distance. inicialmente en 1px. El selector que se muestra en el siguiente ejemplo de código verifica ver si un dispositivo que debe colocar el cursor sobre el botón o lo enfoca un indicador de enfoque y no estar activado. Si es así, aplica CSS para hacer lo siguiente: lo siguiente:

  • Aplica el color de fondo cuando se coloca el cursor sobre un elemento.
  • Aumenta la distancia .
  • Agrega un efecto de aceleración de rebote.
  • Escalonar las transiciones de los seudoelementos.
.threeD-button-set button {
  

  &:is(:hover, :focus-visible):not(:active) {
    /* subtle distance plus bg color change on hover/focus */
    --distance: 15px;
    background-color: var(--_btn-bg-hover);

    /* if motion is OK, setup transitions and increase distance */
    @media (--motionOK) {
      --distance: 3vmax;

      transition-timing-function: var(--_bounce-ease);
      transition-duration: .4s;

      &::after  { transition-duration: .5s }
      &::before { transition-duration: .3s }
    }
  }
}

La perspectiva 3D seguía siendo muy buena para la preferencia de movimiento de reduced. Los elementos inferior y superior muestran el efecto de una manera agradable y sutil.

Pequeñas mejoras con JavaScript

La interfaz se puede utilizar desde teclados, lectores de pantalla, controles de juegos, controles táctiles y del mouse, pero podemos agregar un poco de JavaScript para facilitar diferentes situaciones.

Cómo brindar compatibilidad con teclas de flecha

La tecla Tab es una buena forma de navegar por el menú, pero esperaría que la tecla o joysticks para mover el enfoque en un control de mando. El Biblioteca roving-ux que se usa a menudo para la GUI Las interfaces de desafío se encargarán de las teclas de flecha por nosotros. El siguiente código le indica para capturar el enfoque dentro de .threeD-button-set y reenviarlo al botón secundario.

import {rovingIndex} from 'roving-ux'

rovingIndex({
  element: document.querySelector('.threeD-button-set'),
  target: 'button',
})

Interacción con el paralaje del mouse

Hacer un seguimiento del mouse e inclinar el menú está diseñado para imitar la RA y la RV interfaces de videojuegos, con las que, en lugar de un mouse, puedes tener un puntero virtual. Puede ser divertido cuando los elementos conocen muy bien el puntero.

Como se trata de un atributo adicional pequeño, pondremos la interacción detrás de una consulta de la preferencia de movimiento del usuario. Además, como parte de la configuración, debes almacenar la lista de botones en la memoria con querySelector y almacenar en caché los límites del elemento en menuRect Usa estos límites para determinar el desplazamiento de rotación que se aplica a la tarjeta según la posición del mouse.

const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

A continuación, necesitamos una función que acepte las posiciones x y y del mouse, y muestre un valor que podemos usar para rotar la tarjeta. La siguiente función usa el mouse para identificar en qué lado de la caja se encuentra y en qué medida. El se devuelve como delta desde la función.

const getAngles = (clientX, clientY) => {
  const { x, y, width, height } = menuRect

  const dx = clientX - (x + 0.5 * width)
  const dy = clientY - (y + 0.5 * height)

  return {dx,dy}
}

Por último, observa cómo se mueve el mouse y pasa la posición a nuestra función getAngles(). y usar los valores delta como estilos de propiedad personalizada. Dividí por 20 para rellenar o hacer que sea menos trabas, existe una mejor manera de hacerlo. Si Recuerda que, desde el principio, pusimos los objetos --x y --y en medio de un función clamp(), evita que la posición del mouse gire demasiado el la tarjeta en una posición ilegible.

if (motionOK) {
  window.addEventListener('mousemove', ({target, clientX, clientY}) => {
    const {dx,dy} = getAngles(clientX, clientY)

    menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
    menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
  })
}

Traducciones e instrucciones sobre cómo llegar

Hubo una dificultad al probar el menú del juego en otros modos de escritura y idiomas.

Los elementos <button> tienen un estilo !important para writing-mode en el usuario. hoja de estilo del agente. Esto significaba que se debía cambiar el código HTML del menú del juego para adaptarse a el diseño deseado. Cambiar la lista de botones a una lista de vínculos habilita la propiedades para cambiar la dirección del menú, ya que los elementos <a> no tienen navegador estilo !important proporcionado.

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías?‽ 🙂 ¿Puedes agregar un acelerómetro interacción con el menú, entonces, ¿el menú se rota al mosaico del teléfono? ¿Podemos mejorar? la experiencia sin movimiento?

Diversifiquemos nuestros enfoques y aprendamos todas las formas de desarrollar en la Web. Crear una demostración, twittearme vínculos y la agregaré. a la sección de remixes de la comunidad.

Remixes de la comunidad

Aún no hay nada para ver aquí.