3DGratuit

Hand Tracking UI

Trois boutons de verre (Select, Grab, Point) et un anneau de 24 px qui suit le pointeur dans tout le cadre, saute sur le bouton qui reçoit le focus clavier et se resserre à 80 % quand on valide : une simulation de pointeur de main virtuelle, pilotée par la souris, le doigt ou le clavier — sans caméra ni détection de main, malgré son nom.

JSGestesUI

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

Musée de la Batellerie · borne d'accueilChoisissez votre parcours

Démonstration à la souris du pointeur de la future borne

① Maquette d'une borne de musée pilotée au geste — présentation d'un projet de médiation

QuandLa page du projet montre l'écran d'accueil de la future borne, « Choisissez votre parcours », avec trois boutons « Enfants », « Express », « Complet » ; l'anneau figure la main du visiteur, et la légende le dit : « Démonstration à la souris du pointeur de la future borne ».
PourquoiL'anneau qui suit le pointeur et se resserre au clic montre comment la borne répondra, sans prétendre détecter une main : la légende honnête évite de promettre un suivi de main que la page ne fait pas.
RéglagesRacine en écran de borne (cadre sombre de 6 px, coins de 14 px, fond crème) ; boutons agrandis (16 × 22 px de marge intérieure, 15 px, gras), crème bordés de beige, brun quand ils sont actifs ; anneau et centre recolorés en brun #9a6b3a ; titre et légende posés en absolu dans la zone.
Aide · Réglages d'affichagePointeur agrandi

Un anneau épais suit votre souris et se resserre quand vous cliquez : le pointeur reste facile à repérer sur toutes les pages.

Pointeur agrandi : activé

② Pointeur agrandi pour les personnes malvoyantes — page d'aide d'un site de la mairie

QuandLa page « Réglages d'affichage » présente l'option « Pointeur agrandi » : un anneau épais suit la souris au-dessus de trois boutons « Démarches », « Agenda », « Contact » et se resserre quand on clique.
PourquoiUn anneau épais et un retour visuel au clic rendent le pointeur repérable ; c'est un réglage utile, pas un gadget, et les boutons restent de vrais boutons au clavier.
RéglagesAnneau de 40 px à bord orange #f59e0b de 4 px, recentré par margin: -8px 0 0 -8px (le script le place pour 24 px) ; boutons blancs bordés de gris, bleu #1d4ed8 quand ils sont actifs ; fond clair ; interrupteur « activé » dessiné en CSS.

Diapositive 4 / 12Résultats du trimestre

En direct · pointeur laser activé · 23 participants

③ Pointeur laser virtuel d'une conférence en visio — page d'un logiciel de présentation

QuandLa page du logiciel montre une diapositive « Résultats du trimestre » avec trois zones « Ventes », « Stocks », « Équipe » ; l'anneau rouge suit la souris comme un pointeur laser et marque la zone cliquée.
PourquoiUn anneau qui suit le curseur se voit à distance, même compressé dans un flux vidéo, mieux qu'une flèche de souris ; le marquage de la zone active aide l'orateur à dire « ici ».
RéglagesBoutons transformés en cartes de 118 × 96 px (libellé et chiffre empilés) ; carte active bordée de rouge #ef4444 avec un halo de 3 px ; anneau rouge à centre rouge à 45 % ; diapositive blanche dans un cadre sombre, barre « En direct » en bas.

Comment ça marche

La racine .fx-0048-root (250 px de haut, fond rgb(26, 26, 36)) contient .xr-hand-tracking, une zone de tout le cadre qui centre trois vrais boutons <button type="button"> .xr-hand-btn : fond indigo à 15 %, bord indigo à 30 %, texte #a1a1aa en 0,8rem, coins de 8 px. Au-dessus, l'anneau .xr-hand-cursor : 24 px, bord #6366f1 de 2 px, centre teinté à 30 %, pointer-events: none pour ne jamais gêner le clic. Au départ, il attend sous la rangée de boutons, centré, sans en recouvrir un.

Le script parcourt toutes les racines de la page et branche une instance par racine. À chaque mouvement ou appui du pointeur dans la zone, il place l'anneau centré sous le pointeur (positions left et top) ; quand le pointeur sort, l'anneau reste à sa dernière position. Quand un bouton reçoit le focus au clavier (:focus-visible), l'anneau saute sur son centre. Un clic, Entrée ou Espace marque ce bouton comme actif (classe active et aria-pressed="true", les deux autres repassent à false) et resserre l'anneau à 80 % pendant 200 ms. Mesuré dans Chromium 151 et Firefox 153 : deux Tab placent l'anneau au centre de « Grab », Entrée le marque actif ; deux copies sur une page ont chacune leur anneau.

Le nom du catalogue parle de suivi de main : le code n'utilise ni caméra ni détection de geste. Il montre à quoi ressemblerait un pointeur de main virtuelle, et l'anneau suit tout ce qui pointe : souris, stylet, doigt (touch-action: pan-y garde le défilement vertical, un glissé de côté déplace l'anneau, un appui le pose et active le bouton touché) et focus clavier. Pour un vrai suivi de main, il faudrait une bibliothèque de détection branchée sur la caméra, qui appellerait la même fonction moveTo(x, y) avec la position du doigt. Les boutons sont de vrais boutons : Tab, Entrée et Espace marchent sans code en plus, et les lecteurs d'écran annoncent leur état enfoncé.

Accessibilité

  • Mouvement réduit : sous prefers-reduced-motion: reduce, un bloc limité à .fx-0048-root retire le resserrement de l'anneau (transform: none), ses transitions et le grossissement des boutons au survol. L'anneau suit toujours le pointeur, le retour de clic reste visible par la couleur du bouton actif. Mesuré dans Chromium, Firefox et WebKit : anneau sans changement d'échelle après un clic.
  • Clavier : Tab passe d'un bouton à l'autre, avec un contour #a5b4fc de 2 px (contraste 8,7:1 sur le fond) et l'anneau qui saute sur le bouton ; Entrée ou Espace l'active. Safari ne donne le focus aux boutons par Tab que si l'option clavier du système est activée (mesuré dans WebKit : focus non reçu par défaut) ; Chromium et Firefox le donnent toujours.
  • Lecteurs d'écran : l'anneau est aria-hidden="true" ; les boutons annoncent leur nom et leur état (aria-pressed). Si vos boutons déclenchent une vraie action, gardez aria-pressed pour un choix exclusif, ou retirez-le pour une action ponctuelle.
  • Contraste : texte #a1a1aa sur le bouton à 5,8:1 au repos, 4,1:1 sur le bouton actif ; anneau #6366f1 à 3,9:1 sur le fond, au-dessus du seuil de 3:1 des éléments graphiques.

Compatibilité navigateur

CSS (positionnement absolu, calc(), transition, touch-action, :focus-visible) + JS d'événements pointer, focusin et click, aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26.5 : l'anneau suit la souris, se resserre au clic, deux copies indépendantes ; au clavier dans Chromium et Firefox ; au doigt dans Chromium en mode tactile. Aucune 3D CSS ni WebXR : rien n'est simulé côté navigateur au-delà du pointeur.

Chrome 86+✓ Complet
Firefox 85+✓ Complet
Safari 15.4+✓ Mesuré sur WebKit ; Tab sur les boutons selon l'option clavier du système
Edge 86+✓ Complet
Mobile iOS 15.4+✓ Non vérifié sur un vrai iPhone
Android Chrome✓ Complet, au doigt

Sans :focus-visible (navigateurs plus anciens que ceux listés), l'anneau ne saute plus sur le bouton au clavier mais les boutons restent utilisables ; sans JavaScript, les trois boutons restent cliquables et l'anneau reste immobile sous la rangée.

Le code

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

HTML
<div class="fx-0048-root">
  <div class="xr-hand-tracking">
    <button type="button" class="xr-hand-btn" aria-pressed="false">Select</button>
    <button type="button" class="xr-hand-btn" aria-pressed="false">Grab</button>
    <button type="button" class="xr-hand-btn" aria-pressed="false">Point</button>
    <div class="xr-hand-cursor" aria-hidden="true"></div>
  </div>
</div>
CSS
.fx-0048-root,
.fx-0048-root *,
.fx-0048-root *::before,
.fx-0048-root *::after {
  box-sizing: border-box
}

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

/* Variables du design system du site, dont ce code dépend (valeurs de css/style.css — à adapter librement). */
.fx-0048-root {
  --text-secondary: #a1a1aa;
}

.xr-hand-tracking {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  /* Toucher : un glissé de côté déplace l'anneau, un glissé vertical fait toujours défiler la page */
  touch-action: pan-y;
}

.xr-hand-cursor {
  width: 24px;
  height: 24px;
  border: 2px solid #6366f1;
  border-radius: 50%;
  position: absolute;
  /* Position de départ : sous la rangée de boutons, sans en recouvrir un */
  left: calc(50% - 12px);
  top: calc(50% + 34px);
  pointer-events: none;
  transition: transform 0.1s ease;
}

.xr-hand-cursor::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: rgba(99, 102, 241, 0.3);
}

.xr-hand-btn {
  font-family: inherit;
  padding: 10px 20px;
  background: rgba(99, 102, 241, 0.15);
  border: 1px solid rgba(99, 102, 241, 0.3);
  border-radius: 8px;
  color: var(--text-secondary);
  font-size: 0.8rem;
  cursor: pointer;
  transition: all 0.3s ease;
  margin: 0 8px;
}

.xr-hand-btn:hover {
  background: rgba(99, 102, 241, 0.3);
  transform: scale(1.05);
}

.xr-hand-btn.active {
  background: rgba(99, 102, 241, 0.4);
  border-color: #6366f1;
}

.xr-hand-btn:focus-visible {
  outline: 2px solid #a5b4fc;
  outline-offset: 2px;
}

/* Mouvement réduit : l'anneau suit toujours le pointeur, mais ne se resserre plus et les boutons ne grossissent plus */
@media (prefers-reduced-motion: reduce) {
  .fx-0048-root .xr-hand-cursor {
    transform: none !important;
    transition: none;
  }

  .fx-0048-root .xr-hand-btn {
    transition: none;
  }

  .fx-0048-root .xr-hand-btn:hover {
    transform: none;
  }
}
JavaScript (fx-0048)
(function() {
  // Hand Tracking UI — l'anneau suit la souris, le doigt ou le focus clavier (fx-0048)
  // Une instance par racine .fx-0048-root : plusieurs copies sur une page ont chacune leur anneau.
  document.querySelectorAll('.fx-0048-root').forEach(function(root) {
    var container = root.querySelector('.xr-hand-tracking');
    var cursor = root.querySelector('.xr-hand-cursor');
    if (!container || !cursor) return;
    var buttons = container.querySelectorAll('.xr-hand-btn');

    // x, y : coordonnées dans la fenêtre (pointeur, ou centre du bouton qui a le focus)
    function moveTo(x, y) {
      var rect = container.getBoundingClientRect();
      cursor.style.left = (x - rect.left - 12) + 'px';
      cursor.style.top = (y - rect.top - 12) + 'px';
    }

    function follow(e) {
      moveTo(e.clientX, e.clientY);
    }

    container.addEventListener('pointermove', follow);
    container.addEventListener('pointerdown', follow);

    // Clavier : l'anneau saute sur le bouton qui reçoit le focus (Tab), Entrée ou Espace le valide
    container.addEventListener('focusin', function(e) {
      var btn = e.target.closest('.xr-hand-btn');
      if (!btn || !btn.matches(':focus-visible')) return;
      var r = btn.getBoundingClientRect();
      moveTo(r.left + r.width / 2, r.top + r.height / 2);
    });

    buttons.forEach(function(btn) {
      btn.addEventListener('click', function() {
        buttons.forEach(function(b) {
          b.classList.remove('active');
          b.setAttribute('aria-pressed', 'false');
        });
        btn.classList.add('active');
        btn.setAttribute('aria-pressed', 'true');
        cursor.style.transform = 'scale(0.8)';
        setTimeout(function() {
          cursor.style.transform = 'scale(1)';
        }, 200);
      });
    });
  });
})();

Personnaliser

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

Option / propriétéDéfautEffet
Taille de l'anneau (CSS — width et height de .xr-hand-cursor ; JS — le décalage 12) 24 px Le script centre l'anneau en retirant 12 px : pour un anneau plus grand, changez ces deux 12 en la moitié de la taille, ou compensez par une marge négative comme dans la deuxième scène (40 px, margin: -8px 0 0 -8px).
Couleurs de l'anneau (CSS — border de .xr-hand-cursor, background de son ::after) #6366f1, centre à 30 % Gardez au moins 3:1 de contraste avec le fond pour que l'anneau se repère.
Resserrement au clic (JS — scale(0.8) et 200) 80 % pendant 200 ms 0.6 donne un « pincement » plus marqué ; au-delà de 300 ms, le retour paraît lent.
Position de départ (CSS — left et top de .xr-hand-cursor) centré, 34 px sous le milieu Placez-le là où il ne cache rien. Retirez ces deux lignes et l'anneau partirait au centre exact, sur le bouton du milieu.
Boutons (HTML — texte des <button> ; CSS — .xr-hand-btn et .active) Select, Grab, Point ; indigo à 15, 30 et 40 % Le script prend tous les .xr-hand-btn de la zone, quel que soit leur nombre. Gardez des <button> : le clavier et l'état enfoncé fonctionnent sans code en plus.
Hauteur (CSS — height: 250px de .fx-0048-root) 250 px Toute la zone suit le pointeur : agrandissez-la pour couvrir une section entière.

FAQ

Est-ce que l'effet suit vraiment la main ?

Non. Il n'utilise ni caméra ni détection de geste : l'anneau suit la souris, le stylet, le doigt sur un écran tactile et le focus clavier. Le nom décrit l'usage visé, une interface de main virtuelle ; pour la brancher sur un vrai suivi, il faudrait une bibliothèque de détection qui appelle moveTo(x, y) avec la position du doigt filmé.

Pourquoi l'anneau ne part-il pas du centre ?

Au centre exact, il recouvrirait le bouton du milieu avant tout mouvement et donnerait l'impression qu'il est déjà visé. Il attend donc 34 px sous le milieu de la zone, sous la rangée de boutons ; dès que le pointeur bouge ou qu'un bouton reçoit le focus, il le rejoint.

Puis-je mettre plusieurs zones sur une page ?

Oui. Chaque copie a sa racine .fx-0048-root et le script démarre une instance par racine, sans id : chaque zone a son anneau et ses boutons actifs. C'est le cas sur cette page, où la démo du haut et les trois exemples tournent avec un seul script.