3DGratuit

Spatial UI Panels

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.

CSS 3DPerspectiveUI

Mis à jour le

Survolez ou cliquez la scène pour interagir

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 →

Exemples d'usage

Salle de réalité virtuelle · NantesChoisissez votre mission

Le menu reprend celui du casque : sur place, vous choisirez de la même façon.

① Choisir son expérience dans une salle de réalité virtuelle — page de réservation

QuandTrois panneaux « 🏰 Le Donjon », « 🚀 Station Orion », « 🌊 Abysses » sur fond nuit ; le panneau survolé vient de face avec son nombre de joueurs et sa durée. La page est dans le navigateur : elle imite le menu du casque, elle ne l'utilise pas.
PourquoiL'arc de panneaux reprend la façon dont les menus sont posés autour du joueur dans un casque, ce qui prépare le client à ce qu'il verra sur place.
RéglagesRacine sans hauteur fixe ni fond, qui remplit la scène entre le titre et le pied ; panneaux à 27 % de la largeur (150 px au plus), verre indigo plus marqué, icônes de 30 px ; ligne « 2 à 6 joueurs · 30 min » allumée dans le panneau avancé.

T3 lumineux, 62 m²198 000 €

Angers, Saint-Serge · 3e étage avec ascenseur

Visites le samedi matin · Agence du Port

② Les trois pièces d'un appartement à visiter — annonce d'une agence immobilière

QuandAnnonce « T3 lumineux, 62 m² » : panneaux « 🛋️ Séjour 28 m² », « 🍳 Cuisine 9 m² », « 🛏️ Chambre 12 m² » ; survoler une pièce l'amène de face avec une vignette de la pièce.
PourquoiL'arc donne l'idée d'être au centre du logement, les pièces autour de soi ; un panneau à la fois vient au premier plan.
RéglagesFond crème, verre blanc à 55 % avec bordure brune, texte sombre ; vignette dessinée en dégradés, allumée dans le panneau avancé ; sous 480 px de large, panneaux à 22 % et textes réduits pour que le panneau avancé reste dans la scène.

Serre 2 · tomatesRéglages

Maraîcher des Jardins de la Vallée · dernier relevé il y a 2 min

③ Trois réglages d'une serre connectée — maquette de l'appli d'un maraîcher

QuandPanneaux « 🌡️ Température 22 °C », « 💧 Arrosage 06:00 », « ☀️ Ombrage 40 % » ; celui qu'on survole s'avance avec son curseur de réglage.
PourquoiTrois réglages, pas plus : l'arc met chaque commande à égale distance de l'œil, et le panneau actif se détache sans ouvrir d'écran.
RéglagesFond vert sombre, verre émeraude rgba(16, 185, 129, .14) ; panneaux élargis à 29 % ; curseur dessiné sous la valeur, allumé dans le panneau avancé.

Comment ça marche

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.

Accessibilité

  • Mouvement réduit : sous 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.
  • Clavier : trois vrais boutons, atteints avec Tab ; le focus donne l'état du survol plus un liseré #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.
  • Contraste : libellé #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.
  • Toucher : au doigt, le panneau touché vient de face et l'arc pivote vers lui (mesuré en émulation tactile Chromium à 375 px et dans Firefox) ; le même toucher déclenche aussi le clic du bouton (mesuré en émulation Chromium, non vérifié sur iPhone).

Compatibilité navigateur

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.

Chrome 86+✓ Complet
Firefox 103+✓ Complet (flou du verre depuis la 103)
Safari✓ Non vérifié sur un vrai Safari ; flou du verre sans préfixe depuis Safari 18
Edge 86+✓ Complet
Mobile iOS✓ Non vérifié sur un vrai iPhone
Android Chrome✓ Complet (toucher mesuré en émulation Chromium)

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

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="fx-0041-root">
  <div class="xr-spatial-panels">
    <button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">&#128202;</span>Stats</button>
    <button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">&#9881;</span>Settings</button>
    <button type="button" class="xr-panel"><span class="xr-panel-icon" aria-hidden="true">&#128100;</span>Profile</button>
  </div>
</div>
CSS
.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;
  }
}
JavaScript (fx-0041)
(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 = '';
    });
  });
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
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.

FAQ

Est-ce de la réalité virtuelle (WebXR) ?

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.

Comment fonctionne la parallaxe ?

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.

Puis-je l'utiliser sur un téléphone ?

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.