Créer un composant de menu de jeu en 3D

Découvrez comment créer un menu de jeu 3D responsif, adaptatif et accessible.

Dans ce post, je vais vous expliquer comment créer un composant de menu de jeu en 3D. Essayez le demo.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Démonstration

Si vous préférez la vidéo, voici une version YouTube de cet article:

Présentation

Les jeux vidéo présentent souvent aux utilisateurs un menu créatif et inhabituel, et dans l'espace 3D. Il est populaire dans les nouveaux jeux de RA/RV pour que le menu semble être qui flotte dans l'espace. Aujourd'hui, nous allons recréer les bases de cet effet, mais grâce à un jeu de couleurs qui s'adapte et à des aménagements pour les utilisateurs qui préfèrent les mouvements réduits.

HTML

Un menu de jeu est une liste de boutons. La meilleure façon de représenter cela en HTML est de ce qui suit:

<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>

Une liste de boutons s'annoncera bien aux technologies de lecteur d'écran et fonctionne sans JavaScript ni CSS.

un
une liste à puces d&#39;apparence très générique
avec des boutons normaux comme éléments.

CSS

Le style de la liste de boutons se décompose en grandes étapes suivantes:

  1. Configurer des propriétés personnalisées
  2. Une mise en page Flexbox.
  3. Bouton personnalisé avec des pseudo-éléments décoratifs.
  4. Placer des éléments dans un espace 3D

Présentation des propriétés personnalisées

Les propriétés personnalisées permettent de faire la distinction entre les valeurs à des valeurs d'apparence aléatoire, en évitant les répétitions et le partage des valeurs chez les enfants.

Vous trouverez ci-dessous les requêtes média enregistrées en tant que variables CSS, également appelées requêtes médias. Il s’agit d’entreprises mondiales et sera utilisé dans différents sélecteurs afin que le code reste concis et lisible. La Le composant de menu du jeu utilise des animations préférences, couleur système , et plage de couleurs des fonctionnalités l'écran.

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

Les propriétés personnalisées suivantes permettent de gérer le jeu de couleurs et de maintenir le bouton de la souris enfoncé des valeurs de position permettant de survoler le menu du jeu de façon interactive. Attribution de noms personnalisée aide à la lisibilité du code, car elle révèle le cas d'utilisation de la valeur ou nom convivial pour le résultat de la valeur.

.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);
    }
  }
}

Arrière-plans coniques avec thème clair et sombre

Le thème clair présente une conique entre cyan et deeppink . dégradé tandis que le thème sombre présente un dégradé conique sombre et subtil. Pour en savoir plus sur peut être réalisé avec des dégradés coniques (voir conic.style).

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

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
<ph type="x-smartling-placeholder">
</ph>
. Démonstration du changement de couleur d'arrière-plan entre les préférences de couleur claire et sombre.

Activer la perspective 3D

Pour que les éléments existent dans l'espace 3D d'une page Web, une fenêtre d'affichage avec perspective doit être initialisé. J'ai choisi de placer la perspective sur l'élément body et utilisé des blocs de fenêtres d'affichage pour créer le style qui me plaisait.

body {
  perspective: 40vw;
}

C'est le type d'impact que la perspective peut avoir.

Appliquer un style à la liste de boutons <ul>

Cet élément est responsable de la disposition générale des macros de la liste de boutons, ainsi que une carte flottante interactive en 3D. Voici un moyen d'y parvenir.

Disposition du groupe de boutons

Flexbox peut gérer la mise en page du conteneur. Modifier la direction par défaut de l'environnement flexible des lignes aux colonnes avec flex-direction et s'assurer que chaque élément a la taille son contenu en passant de stretch à start pour 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;
}

Définissez ensuite le conteneur en tant que contexte d'espace 3D et configurez CSS clamp(). pour s'assurer que la rotation de la carte n'est pas au-delà des rotations lisibles. Avertissement que la valeur du milieu de la limitation est une propriété personnalisée, ces valeurs --x et --y sont définies à partir de JavaScript au moment de la souris. plus tard.

.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)
      )
    )
  ;
}

Ensuite, si l'animation convient à l'utilisateur visiteur, ajoutez au navigateur un indice la transformation de cet élément change constamment will-change Vous pouvez également activer l'interpolation en définissant un transition pour les transformations. Ce se produit lorsque la souris interagit avec la carte, ce qui permet avant les changements de rotation. Il s'agit d'une animation qui s'exécute en permanence montrant l'espace 3D dans lequel se trouve la carte, même si une souris ne le permet pas, ou n'interagit pas avec le composant.

@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;
  }
}

L'animation rotate-y ne définit l'image clé du milieu que sur 50%, car la le navigateur utilise par défaut 0% et 100% sur le style par défaut de l'élément. Ce est un raccourci pour les animations qui alternent, qui doivent commencer et se terminer au même la position de votre annonce. C'est un excellent moyen d'articuler des animations alternées infinies.

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

Appliquer un style aux éléments <li>

Chaque élément de la liste (<li>) contient le bouton et ses bordures. La Le style display a été modifié afin que l'élément n'affiche pas de ::marker Style position est défini sur relative afin que les pseudo-éléments du bouton "Suivant" puissent se positionner ; dans toute la zone que le bouton utilise.

.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;
}

Une capture d&#39;écran de la liste pivotée dans l&#39;espace 3D pour afficher la perspective et
chaque élément de la liste 
ne comporte plus de puce.

Appliquer un style aux éléments <button>

Appliquer un style aux boutons peut être difficile, il existe de nombreux états et types d'interactions à prendre en compte. Ces boutons deviennent rapidement complexes en raison de l'équilibrage les pseudo-éléments, les animations et les interactions.

Styles <button> initiaux

Vous trouverez ci-dessous les styles de base compatibles avec les autres états.

.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;
}

Capture d&#39;écran de la liste de boutons en perspective 3D, cette fois avec un style stylisé
.

Pseudo-éléments de bouton

Les bordures du bouton ne sont pas des bordures traditionnelles. Elles sont positionnées de façon absolue. des pseudo-éléments avec des bordures.

Capture d&#39;écran du panneau &quot;Éléments des outils pour les développeurs Chrome&quot; avec un bouton affichant
::avant et ::after.

Ces éléments sont essentiels pour montrer la perspective 3D qui a été établi. L'un de ces pseudo-éléments est supprimé du bouton, et l'une d'entre elles se rapproche de l'utilisateur. L'effet est particulièrement visible boutons supérieur et inférieur.

.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);
    }
  }
}

Styles de transformation 3D

Sous transform-style est défini sur preserve-3d afin que les enfants puissent l'espacer sur l'axe z. transform est défini sur --distance personnalisée, qui augmentera en cas de passage de la souris et d'attention.

.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));
  }
}

Styles d'animation conditionnelle

Si l'utilisateur est d'accord avec les mouvements, le bouton indique au navigateur que le la propriété de transformation doit être prête à être modifiée et une transition est définie pour transform et background-color. Notez la différence la durée, j'ai senti que c'était un joli effet échelonné.

.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 }
  }
}

Styles d'interaction avec la souris et le curseur

L'objectif de l'animation d'interaction est de répartir les couches qui composent le un bouton plat qui apparaît. Pour ce faire, définissez la variable --distance, initialement sur 1px. Le sélecteur présenté dans l'exemple de code suivant vérifie vérifiez si un appareil passe le curseur sur le bouton ou le sélectionne un indicateur de mise au point et n'est pas activé. Si c'est le cas, le code CSS suivantes:

  • Appliquez la couleur d'arrière-plan de l'élément de pointage.
  • Augmentez la distance .
  • Ajoutez un effet de lissage de vitesse.
  • Échelonnez les transitions de pseudo-éléments.
.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 perspective 3D était tout de même intéressante pour la préférence de mouvement reduced. Les éléments supérieurs et inférieurs montrent l'effet d'une manière subtile.

Petites améliorations avec JavaScript

L'interface peut être utilisée à l'aide d'un clavier, d'un lecteur d'écran, d'une manette de jeu, d'un écran tactile la souris, mais nous pouvons ajouter quelques petites touches de JavaScript pour de scénarios.

Compatibilité avec les touches fléchées

La touche Tab permet de naviguer très bien dans le menu, mais je m'attends à ce que la direction manette de jeu ou joysticks pour déplacer la sélection sur la manette. La Bibliothèque roving-ux souvent utilisée pour l'IUG Les interfaces de défi géreront les touches fléchées à notre place. Le code ci-dessous indique au bibliothèque pour piéger le curseur dans .threeD-button-set et le transférer à la bouton enfant.

import {rovingIndex} from 'roving-ux'

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

Interaction avec le parallaxe de la souris

Le suivi de la souris et l'inclinaison du menu sont destinés à imiter la RA et la RV. des interfaces de jeux vidéo, où un pointeur virtuel peut se trouver au lieu d'une souris. Cela peut être amusant lorsque les éléments sont très conscients du pointeur.

Puisqu'il s'agit d'une petite fonctionnalité supplémentaire, nous allons placer l'interaction derrière une requête de les préférences de mouvement de l'utilisateur. Lors de la configuration, stockez la liste des boutons en mémoire avec querySelector et mettre en cache les limites de l'élément dans menuRect Utilisez ces limites pour déterminer le décalage de rotation appliqué à la carte. en fonction de la position de la souris.

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

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

Ensuite, nous avons besoin d'une fonction qui accepte les positions x et y de la souris et qui renvoie une valeur que nous pouvons utiliser pour faire pivoter la carte. La fonction suivante utilise la souris position pour déterminer de quel côté de la boîte il se trouve et dans quelle mesure. La delta est renvoyée par la fonction.

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}
}

Enfin, observez le mouvement de la souris et transmettez la position à notre fonction getAngles(). et utiliser les valeurs delta comme styles de propriétés personnalisés. divisé par 20 pour remplir delta et de le rendre moins nerveux, il existe peut-être une meilleure façon de le faire. Si vous rappelez-vous depuis le début que nous plaçons les accessoires --x et --y au milieu d'une clamp(), cela évite que la position de la souris ne fasse trop pivoter la dans une position illisible.

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`)
  })
}

Traductions et instructions

Il y avait un problème en testant le menu du jeu dans d'autres modes d'écriture et langues.

Les éléments <button> ont un style !important pour writing-mode dans l'utilisateur la feuille de style de l'agent. Cela signifiait que le code HTML du menu du jeu devait changer pour s'adapter la conception souhaitée. Le fait de remplacer la liste de boutons par une liste de liens active une logique pour changer le sens d'écriture du menu, car les éléments <a> n'ont pas de navigateur style !important fourni.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 😃 Peux-tu ajouter un accéléromètre ? interaction avec le menu, donc le fait de disposer votre téléphone fait pivoter le menu ? Pouvons-nous améliorer l'expérience sans mouvement ?

Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web. Créer une démonstration, me envoyer des tweets et je l'ajouterai à la section des remix de la communauté ci-dessous.

Remix de la communauté

Rien à afficher pour le moment !