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">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.
CSS
Le style de la liste de boutons se décompose en grandes étapes suivantes:
- Configurer des propriétés personnalisées
- Une mise en page Flexbox.
- Bouton personnalisé avec des pseudo-éléments décoratifs.
- 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);
}
}
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;
}
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;
}
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.
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 !