Comment créer un menu CSS en 3D ?

Comment créer un menu CSS en 3D ?

Les nouveaux jeux AR/VR donnent souvent l’impression que le menu flotte dans les airs. Recréez la base de cet effet, ajoutez une palette de couleurs adaptative et considérez les utilisateurs qui préfèrent moins d’animation. Le menu fonctionnera avec écran, manette de jeu, entrée tactile et plus encore.

Examen

Ce guide utilise des CSS expérimentaux @custom-media et @nest pour éviter que les requêtes médias ne soient répétées et placées dans des blocs de style composant. La syntaxe proposée dans ces spécifications est supportée par PostCSS et ces deux plugins : postcss-custom-media et postcss-nesting.

=== Service sur notre site d’édition HTML et CSS ===

HTML de menu CSS

Le menu CSS du jeu est une liste de boutons. En HTML, la meilleure façon de le présenter est :

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

La liste des boutons est bien reçue par les technologies de lecture d’écran et fonctionne sans JavaScript ou CSS.

menu css

menu CSS

Haut niveau de style de la liste de boutons se compose de:

  • propriétés personnalisées;
  • conteneur flexible;
  • boutons personnalisés avec des pseudo-éléments décoratifs;
  • placez les éléments en 3D.

Aperçu des propriétés de l’utilisateur

Les propriétés personnalisées aident à éliminer l’ambiguïté des valeurs en donnant des noms significatifs aux significations qui semblent aléatoires sans elles, ainsi qu’en évitant de répéter du code et de partager la même valeur entre les éléments enfants.

Ce qui suit sont des requêtes de médias, également connu sous le nom de médias utilisateurs. Ils sont enregistrés comme variables CSS. Ils sont globaux et seront utilisés dans différents sélecteurs pour garder le code court et lisible. Le composant du menu de jeu utilise des fonctions de préférences de mouvement réduit, de palette de couleurs du système et de plage de couleurs d’affichage :

@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 contrôlent le jeu de couleurs et contiennent les valeurs de position de la souris qui donnent notre interactivité de menu. Nommer les propriétés de l’utilisateur aide à comprendre le code en montrant une application ou un nom de valeur compréhensible.

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

Thèmes et gradient conique en menu CSS

Le thème de lumière a un gradient conique lumineux du bleu au violet, et le thème sombre a un gradient conique sombre et mince. Pour en savoir plus sur ce qu’il faut faire avec les gradients 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);
  }
}

Inclusion de la perspective 3D en menu CSS

Pour que les éléments se trouvent dans un espace de page Web tridimensionnel, il est nécessaire d’initialiser la fenêtre de visualisation avec perspective. Nous avons décidé de mettre la perspective dans l’élément corps et d’utiliser des unités de fenêtre de visualisation:

body {
  perspective: 40vw;
}

C’est l’effet en menu CSS, de la perspective.

Mise en page du groupe de boutons

Flexbox en menu CSS, peut contrôler la disposition des conteneurs. Changez la direction flex par défaut des lignes aux colonnes via la propriété flex-direction et donnez à chaque élément une taille en fonction de son contenu, en définissant align-items : flex-start :

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

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

Réglez le conteneur sur un contexte d’espace en trois dimensions et configurez la fonction CSS clamp() pour empêcher la carte de tourner hors de vue. Notez que la valeur moyenne pour clamp est une propriété personnalisée, ces valeurs (-x et –y) seront définies à partir de JavaScript plus tard dans l’interaction avec la souris :

.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 le mouvement est pratique pour le visiteur du site, ajoutez un indice au navigateur (menu CSS) que la transformation de cet élément changera constamment à l’aide de la volonté-changement. Aussi, activez l’interpolation, définissant la transition pour la transformation. 

Cette transition se produit lorsque la souris interagit avec la carte, assurant une transition en douceur aux changements de rotation. L’animation sera réalisée en continu et montrera l’espace tridimensionnel dans lequel se trouve la carte, même si la souris 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 que 50 % de l’image clé du milieu, le navigateur par défaut définissant 0 % et 100 % selon le style d’élément par défaut. C’est un raccourci pour alterner les animations en menu CSS, qui doivent commencer et finir dans la même position. Il s’adapte parfaitement pour former une animation infinie avec alternance :

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

Style des éléments <li> en menu CSS

Chaque élément de liste (<li>) contient un bouton et ses éléments de bordure. Le style d’affichage est modifié de sorte que l’élément n’affiche pas ::marker. La propriété position est définie sur relative afin que les pseudo-éléments à venir du bouton soient positionnés dans toute la zone du bouton :

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

Éléments de style <bouton> en menu CSS

Le style des boutons peut être difficile parce que de nombreux états et types d’interaction doivent être considérés. Les causes de difficulté sont l’équilibre des pseudo-éléments, des animations et des interactions.

Styles principaux <button> en menu CSS. Voici les principaux styles des 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;
}
menu css

Boutons pseudo-éléments

Les bordures de boutons ne sont pas des bordures standard, mais des pseudo-éléments avec position : absolue et bordures.

menu css

Ces éléments sont essentiels pour démontrer une perspective tridimensionnelle. L’un de ces pseudo-éléments s’éloignera du bouton, l’autre s’en approchera. L’effet est plus perceptible sur les boutons haut et bas :

.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 en menu CSS

Ci-dessous transform-style est défini pour préserver-3d afin que les éléments enfants puissent être placés sur l’axe z. La propriété –distance personnalisée est définie pour transform. Cette distance augmentera avec le vol stationnaire et la mise au point :

.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 conditionnels en menu CSS

Si l’utilisateur choisit de se déplacer, le bouton indique au navigateur que la propriété transform doit être prête à changer : pour les propriétés transform et background-color, la transition est définie. Notez la différence de longueur : nous avons pensé qu’il avait un bel effet de pas mince :

.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 de communication Hover et Focus

Le but de l’animation d’interaction est d’étendre les calques qui composent le bouton plat. Il est réalisé en installant une variable –distance, initialement 1px. Le sélecteur montré dans l’exemple suivant de code vérifie le focus et le guidage. Et, si un bouton dans le foyer ou le curseur est monté sur un bouton, applique CSS (sur menu CSS) pour faire ce qui suit:

  • appliquer la couleur de fond du guide;
  • augmenter la distance;
  • ajouter un effet de rebond léger;
  • accélérer les transitions des pseudo-éléments.

La variation transition-durée n’est utilisée qu’en vol stationnaire, et l’étape d’animation n’est appliquée qu’en vol stationnaire. Lorsque le guidage ou le foyer est retiré, chaque couche passe à l’état standard :

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

Une perspective 3D est bonne si l’utilisateur préfère moins d’animation. Les éléments haut et bas montrent un effet agréable.

Petites améliorations avec JavaScript en menu CSS

L’interface peut déjà être utilisée depuis le clavier, les économiseurs d’écran, le gamepad, l’entrée tactile et la souris, mais nous pouvons ajouter quelques lightstrokes JavaScript.

Support des touches fléchées
La touche de tabulation est une excellente façon de naviguer dans le menu, mais il est souhaitable que la mise au point soit déplacée par le curseur ou les manettes sur la manette de jeu. La bibliothèque roving-ux, souvent utilisée pour les interfaces GUI Challenge sur menu CSS, gère les touches fléchées. Le code suivant indique à la bibliothèque d’intercepter le focus dans . threeD-button-set et de le rediriger vers les boutons enfants :

import {rovingIndex} from 'roving-ux'

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

Interaction parallaxe de la souris en menu CSS

Le suivi de la souris et l’inclinaison du menu sont conçus pour simuler les interfaces de jeux vidéo AR et VR, où vous pouvez avoir un pointeur virtuel au lieu d’une souris. Il peut être amusant lorsque les éléments sont bien conscients du pointeur.

Comme il s’agit d’une petite fonctionnalité supplémentaire, nous placerons l’interaction derrière la demande de préférences utilisateur sur les mouvements. De plus, enregistrez le composant de liste de boutons en mémoire avec querySelector et mettez en cache les bordures des éléments dans menuRect (menu CSS). Utilisez ces bordures pour déterminer le déplacement 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 prend les positions de la souris x et y et retourne la valeur que nous pouvons utiliser pour faire pivoter la carte. La fonction suivante utilise la position de la souris pour déterminer où elle se trouve et combien de temps. Delta revient de 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}
}

Suivez le mouvement de la souris, transférez la position à la fonction getAngles() et utilisez les valeurs delta comme styles de propriétés personnalisés. On l’a divisé par 20 pour lisser le delta et le rendre moins agité, donc peut-être qu’il y a une meilleure façon de le faire. Comme vous vous en souviendrez, au tout début nous avons placé des éléments –x et –y au milieu de la fonction clamp(), ceci empêche la carte d’être transformée en 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`)
  })
}

C’est tout pour aujourd’hui. J’espère que cet article – « Comment créer un menu CSS en 3D?« , a été utile et rendu votre site plus beau! 

  • Partager: