Start · Sprachen · JavaScript · Referenz · XRInputSource.hand

XRInputSource.hand

Eigenschaft

Die schreibgeschützte Eigenschaft <code>hand</code> liefert ein <code>XRHand</code>-Objekt mit den Gelenken einer getrackten Hand oder <code>null</code>, wenn die Eingabequelle keine Hand ist.

seit JavaScript WebXR Hand Input Module Level 1 – Chromium-basiert Kategorie: webapi

Signatur

XRInputSource.hand

Beschreibung

Die Eigenschaft XRInputSource.hand gibt Zugriff auf die Daten des Hand-Trackings in einer WebXR-Sitzung. Ist die Eingabequelle eine vom Gerät erkannte Hand statt eines Controllers, enthält hand ein XRHand-Objekt. Dieses ist eine Map-ähnliche Sammlung aus 25 XRJointSpace-Einträgen, z. B. "wrist", "thumb-tip" oder "index-finger-tip". Bei klassischen Controllern, Blick- oder Bildschirmeingaben ist der Wert null.

Damit der Wert gefüllt wird, muss die Sitzung mit dem Feature "hand-tracking" angefordert werden, entweder über requiredFeatures oder über optionalFeatures bei navigator.xr.requestSession(). Ohne dieses Feature bleibt hand auch bei erkannten Händen null.

Die Gelenk-Räume selbst enthalten keine Positionen. Positionen und Radien liest du pro Frame aus: für ein einzelnes Gelenk mit XRFrame.getJointPose(), für alle Gelenke auf einmal und deutlich schneller mit XRFrame.fillPoses() bzw. XRFrame.fillJointRadii(). Typische Anwendungen sind das Darstellen virtueller Hände, das Erkennen von Gesten wie Pinch oder Faust und das direkte Anfassen von Objekten.

Rückgabewert

Typ
XRHand | null
Beschreibung
Ein XRHand-Objekt (Map-ähnlich, Schlüssel sind Gelenknamen, Werte sind XRJointSpace) oder null, wenn die Eingabequelle keine Hand ist oder kein Hand-Tracking angefordert wurde.

Beispiele

Sitzung mit Hand-Tracking starten und Hände erkennen

const startXR = async () => {
  if (!navigator.xr || !(await navigator.xr.isSessionSupported('immersive-vr'))) {
    console.log('WebXR VR wird nicht unterstützt');
    return;
  }

  const session = await navigator.xr.requestSession('immersive-vr', {
    optionalFeatures: ['hand-tracking']
  });

  session.addEventListener('inputsourceschange', (event) => {
    for (const source of event.added) {
      if (source.hand) {
        console.log(`Hand erkannt: ${source.handedness}, ${source.hand.size} Gelenke`);
      } else {
        console.log(`Controller erkannt: ${source.handedness}`);
      }
    }
  });
};

document.querySelector('#vr-button').addEventListener('click', startXR);
Hand erkannt: left, 25 Gelenke Hand erkannt: right, 25 Gelenke

Pinch-Geste (Daumen und Zeigefinger) erkennen

const PINCH_THRESHOLD = 0.015; // 1,5 cm

const isPinching = (frame, hand, refSpace) => {
  const thumb = frame.getJointPose(hand.get('thumb-tip'), refSpace);
  const index = frame.getJointPose(hand.get('index-finger-tip'), refSpace);
  if (!thumb || !index) return false; // Gelenke gerade nicht getrackt

  const a = thumb.transform.position;
  const b = index.transform.position;
  const distance = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
  return distance < PINCH_THRESHOLD;
};

// innerhalb der Render-Schleife
const onXRFrame = (time, frame) => {
  const session = frame.session;
  session.requestAnimationFrame(onXRFrame);

  for (const source of session.inputSources) {
    if (source.hand && isPinching(frame, source.hand, xrRefSpace)) {
      console.log(`Pinch mit der ${source.handedness === 'left' ? 'linken' : 'rechten'} Hand`);
    }
  }
};
Pinch mit der rechten Hand

Alle Gelenkposen effizient mit fillPoses() lesen

const JOINT_COUNT = 25;
const poses = new Float32Array(JOINT_COUNT * 16); // je Gelenk eine 4x4-Matrix
const radii = new Float32Array(JOINT_COUNT);

const updateHand = (frame, source, refSpace) => {
  const hand = source.hand;
  if (!hand) return;

  const joints = [...hand.values()];
  const posesOk = frame.fillPoses(joints, refSpace, poses);
  const radiiOk = frame.fillJointRadii(joints, radii);

  if (posesOk && radiiOk) {
    // Handgelenk ist Gelenk 0, Translation steht in Matrix-Index 12–14
    const [x, y, z] = poses.slice(12, 15);
    console.log(`Handgelenk bei (${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)}), Radius ${radii[0].toFixed(3)} m`);
  }
};
Handgelenk bei (0.21, 1.05, -0.34), Radius 0.021 m

// Wichtig · Fallstricke

  • Feature anfordern: Ohne "hand-tracking" in requiredFeatures oder optionalFeatures ist hand immer null. Nutze besser optionalFeatures, damit die Sitzung auch auf Geräten ohne Hand-Tracking startet.
  • Immer auf null prüfen: Nutzer können jederzeit zwischen Controllern und Händen wechseln. Dabei feuert inputsourceschange, und es entstehen neue XRInputSource-Objekte. Halte daher keine veralteten Referenzen fest.
  • Posen können fehlen: getJointPose() gibt undefined zurück, wenn ein Gelenk verdeckt ist oder nicht getrackt wird. fillPoses() gibt in diesem Fall false zurück.
  • Leistung: Wenn du pro Frame alle 25 Gelenke brauchst, verwende fillPoses() statt 25 einzelner Aufrufe von getJointPose().
  • Kompatibilität: Nur in sicheren Kontexten (HTTPS) verfügbar und nur in immersiven Sitzungen. Unterstützt wird das Hand-Tracking vor allem im Meta Quest Browser, in Chromium-basierten XR-Browsern und in Safari auf visionOS. Firefox und Desktop-Browser ohne XR-Hardware unterstützen es nicht.
  • Datenschutz: Hand-Tracking-Daten gelten als sensibel, weil sich daraus biometrische Merkmale ableiten lassen. Browser können deshalb eine gesonderte Zustimmung verlangen.