Salle de réalité virtuelle · NantesChoisissez votre mission
Le menu reprend celui du casque : sur place, vous choisirez de la même façon.
Trois panneaux de verre (icône et libellé) disposés en arc dans l'espace — côtés tournés de ±25°, centre avancé — ; le panneau survolé, touché ou atteint au clavier se met de face et avance (70 px, ×1,1), et tout l'arc pivote un peu sous le pointeur, ce qui décale les panneaux selon leur profondeur : un menu « spatial » en CSS 3D et 1,2 Ko de JavaScript, dans une page web ordinaire, sans casque ni WebXR.
Mis à jour le
Cet effet est gratuit — la catégorie 3D en contient 50 au total, dont 8 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
Salle de réalité virtuelle · NantesChoisissez votre mission
Le menu reprend celui du casque : sur place, vous choisirez de la même façon.
T3 lumineux, 62 m²198 000 €
Angers, Saint-Serge · 3e étage avec ascenseur
Visites le samedi matin · Agence du Port
Serre 2 · tomatesRéglages
Maraîcher des Jardins de la Vallée · dernier relevé il y a 2 min
La racine .fx-0041-root (pleine largeur, 250 px de haut, fond rgb(26, 26, 36), overflow: hidden) porte perspective: 800px et la variable --text-secondary. Dedans, .xr-spatial-panels occupe toute la place, centre ses enfants avec 20 px d'écart et passe en transform-style: preserve-3d : c'est le groupe qui pivote. Les trois panneaux sont des <button class="xr-panel"> de 80 × 100 px : verre indigo (rgba(99, 102, 241, .15), bordure à 30 %, backdrop-filter: blur(10px)), coins de 12 px, une icône emoji cachée aux lecteurs d'écran et un libellé.
L'arc est écrit panneau par panneau : le premier prend rotateY(-25deg) translateZ(30px), le deuxième translateZ(50px), le troisième rotateY(25deg) translateZ(30px). Au survol (ou :focus-visible), le panneau se met de face, avance à 70 px et grossit de 10 %, en 0,5 s avec un départ rapide (cubic-bezier(.23, 1, .32, 1)). Le script, lui, cherche chaque .fx-0041-root de la page et suit le pointeur : il calcule sa position dans la racine (de −0,5 à +0,5) et fait pivoter le groupe de rotateX jusqu'à 6° et rotateY jusqu'à 8° — le côté du pointeur recule —, en 0,4 s ; quand le pointeur sort, l'arc revient de face.
C'est ce pivot qui donne la parallaxe : les panneaux étant à 30, 50 ou 70 px de profondeur dans le même groupe, ils ne se déplacent pas d'autant. Mesuré dans Chromium 151 et Firefox 153, pointeur du bord gauche au bord droit : le panneau du centre glisse de 13 px, ceux des côtés de 2,6 px. Pour que le groupe puisse pivoter d'un bloc, la perspective est sur la racine et preserve-3d sur le groupe. Au doigt, pointerdown fait pivoter l'arc vers le point touché, et il y reste jusqu'au toucher suivant. Deux copies sur une page ont chacune leur instance : mesuré, seule la copie survolée pivote. Ce n'est ni de la réalité virtuelle ni du WebXR : une page web dont la mise en scène reprend les menus en arc des casques.
prefers-reduced-motion: reduce, le script ne fait plus pivoter l'arc, et un bloc limité à .fx-0041-root garde chaque panneau à sa place : au survol ou au focus, il s'allume (fond indigo à 35 %, bordure rgba(165, 180, 252, .8), texte blanc), sans transition de position. Mesuré dans Chromium et Firefox : arc et panneau immobiles, 4,9 % de pixels changés au survol.#a5b4fc de 2 px. Les icônes sont marquées aria-hidden="true" : le lecteur d'écran lit « Stats », « Settings », « Profile ». Dans Safari, Tab ne s'arrête sur les boutons que si l'option « Appuyer sur Tab pour mettre en évidence chaque élément » est cochée (sinon Option + Tab) — réglage du navigateur, mesuré dans WebKit.#a1a1aa à 5,7:1 sur le verre posé sur le fond d'origine ; texte blanc à 11,2:1 sur le panneau allumé du mouvement réduit. Le libellé fait 0,65 rem (10,4 px) : pour un vrai menu, montez-le à 0,8 rem au moins.CSS 3D (perspective sur la racine, preserve-3d sur le groupe, trois boutons transformés), backdrop-filter, :focus-visible + 1,2 Ko de JavaScript (Pointer Events, matchMedia), aucune dépendance. Mesuré dans Chromium 151 et Firefox 153 : même arc, même parallaxe (13 px au centre), même clavier, même mouvement réduit, deux copies indépendantes, 0 erreur ; démo du haut et scènes vérifiées à 1 280 et 375 px de large. Le WebKit de Playwright aplatit toute 3D en preserve-3d : rendu Safari non vérifié sur un vrai Safari.
Sans JavaScript, l'arc ne pivote plus, mais les panneaux restent en arc et le survol les met toujours de face : seule la parallaxe disparaît. Sans backdrop-filter, le verre reste un aplat indigo translucide. Pour Safari avant la version 18, ajoutez -webkit-backdrop-filter: blur(10px).
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="fx-0041-root">
<div class="xr-spatial-panels">
<button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">📊</span>Stats</button>
<button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">⚙</span>Settings</button>
<button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">👤</span>Profile</button>
</div>
</div>
.fx-0041-root,
.fx-0041-root *,
.fx-0041-root *::before,
.fx-0041-root *::after {
box-sizing: border-box
}
.fx-0041-root {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
perspective: 800px;
}
/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.fx-0041-root {
--text-secondary: #a1a1aa;
}
/* Tout l'arc s'incline un peu vers le pointeur (JS) : les panneaux, à des profondeurs différentes, se décalent = parallaxe. */
.xr-spatial-panels {
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.4s ease-out;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
.xr-panel {
width: 80px;
height: 100px;
background: rgba(99, 102, 241, 0.15);
border: 1px solid rgba(99, 102, 241, 0.3);
border-radius: 12px;
backdrop-filter: blur(10px);
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 0.65rem;
color: var(--text-secondary);
text-align: center;
font-family: inherit;
cursor: pointer;
transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
}
.xr-panel:nth-child(1) {
transform: rotateY(-25deg) translateZ(30px);
}
.xr-panel:nth-child(2) {
transform: rotateY(0deg) translateZ(50px);
}
.xr-panel:nth-child(3) {
transform: rotateY(25deg) translateZ(30px);
}
.xr-panel:hover,
.xr-panel:focus-visible {
transform: rotateY(0deg) translateZ(70px) scale(1.1);
}
.xr-panel-icon {
font-size: 1.5rem;
margin-bottom: 6px;
}
.xr-panel:focus-visible {
outline: 2px solid #a5b4fc;
outline-offset: 3px;
}
/* Mouvement réduit : pas d'inclinaison, et le panneau choisi reste à sa place ; il s'allume à la place. */
@media (prefers-reduced-motion: reduce) {
.fx-0041-root .xr-spatial-panels { transform: none; transition: none; }
.fx-0041-root .xr-panel { transition: background-color 0.2s, border-color 0.2s, color 0.2s; }
.fx-0041-root .xr-panel:nth-child(1):hover,
.fx-0041-root .xr-panel:nth-child(1):focus-visible { transform: rotateY(-25deg) translateZ(30px); }
.fx-0041-root .xr-panel:nth-child(2):hover,
.fx-0041-root .xr-panel:nth-child(2):focus-visible { transform: rotateY(0deg) translateZ(50px); }
.fx-0041-root .xr-panel:nth-child(3):hover,
.fx-0041-root .xr-panel:nth-child(3):focus-visible { transform: rotateY(25deg) translateZ(30px); }
.fx-0041-root .xr-panel:hover,
.fx-0041-root .xr-panel:focus-visible {
background: rgba(99, 102, 241, 0.35);
border-color: rgba(165, 180, 252, 0.8);
color: #fff;
}
}
(function() {
// ============================================
// SPATIAL UI PANELS - parallaxe : l'arc pivote un peu sous le pointeur
// ============================================
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.fx-0041-root').forEach(function(root) {
var arc = root.querySelector('.xr-spatial-panels');
if (!arc) return;
// Le côté du pointeur recule (jusqu'à 8° à gauche et à droite, 6° en haut et en bas) ; les panneaux avancés (50 à 70 px) se déplacent plus que ceux du fond.
function tilt(e) {
if (reduce.matches) return;
var r = root.getBoundingClientRect();
var x = (e.clientX - r.left) / r.width - 0.5;
var y = (e.clientY - r.top) / r.height - 0.5;
arc.style.transform = 'rotateX(' + (-y * 12).toFixed(2) + 'deg) rotateY(' + (x * 16).toFixed(2) + 'deg)';
}
root.addEventListener('pointermove', tilt);
root.addEventListener('pointerdown', tilt); // au doigt : l'arc pivote vers le point touché et y reste
root.addEventListener('pointerleave', function(e) {
if (e.pointerType !== 'touch') arc.style.transform = '';
});
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Ouverture de l'arc (CSS — rotateY des panneaux 1 et 3) |
−25° et 25° | ±35° referme l'arc autour du spectateur ; ±10° le rend presque plat. Un 4e ou un 5e panneau demande sa propre ligne :nth-child, avec un angle plus ouvert vers les bords. |
Profondeurs (CSS — translateZ au repos et au survol) |
30 / 50 px au repos ; 70 px au survol | Plus les écarts sont grands, plus la parallaxe se voit : avec 20 / 60 / 100 px, le panneau du centre glisse davantage que ceux des côtés. |
Force de la parallaxe (JS — * 12 et * 16) |
6° et 8° au bord de la racine | Doublez les deux pour un effet marqué ; mettez 0 pour un arc fixe qui ne garde que le survol. |
Zoom du panneau choisi (CSS — scale(1.1)) |
×1,1 | ×1,2 met nettement le panneau en avant, mais il peut alors être rogné par la racine (overflow: hidden) sur un petit écran. |
Verre (CSS — background, border, backdrop-filter de .xr-panel) |
indigo à 15 %, bordure à 30 %, flou de 10 px | Le flou ne se voit que si quelque chose passe derrière : posez la racine sur une image ou un dégradé. Sur un fond clair, passez à un verre blanc à 50-60 % et un texte sombre. |
Taille (CSS — height de la racine, width et height des panneaux) |
250 px ; 80 × 100 px | Les scènes donnent aux panneaux une largeur en pourcentage (27 %, 22 % sur téléphone) pour qu'ils tiennent à toutes les tailles. |
Non. C'est une page web ordinaire : trois boutons transformés en CSS 3D et un petit script qui fait pivoter l'arc sous le pointeur. Aucun casque, aucune API WebXR, rien à autoriser. Le nom « Spatial UI » vient de la mise en scène, qui reprend les menus en arc des casques : c'est utile pour présenter une expérience en réalité virtuelle ou donner un air « spatial » à un choix de trois options.
Le script ne déplace aucun panneau : il fait pivoter leur groupe de quelques degrés, le côté où se trouve le pointeur reculant. Comme les panneaux sont à des profondeurs différentes dans ce groupe en preserve-3d, la rotation les décale de quantités différentes : mesuré, 13 px pour le panneau du centre contre 2,6 px pour les côtés, d'un bord à l'autre. C'est le même principe qu'un paysage qui défile plus vite au premier plan.
Oui : au doigt, le panneau touché vient de face et l'arc pivote vers lui, et le toucher déclenche le bouton. Gardez des panneaux en pourcentage de la largeur (environ 22 % sous 480 px, comme dans les scènes) : le panneau avancé grossit, et la racine, en overflow: hidden, rogne ce qui dépasse.