Signatur
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
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);
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`);
}
}
};
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`);
}
};
// Wichtig · Fallstricke
- Feature anfordern: Ohne
"hand-tracking"inrequiredFeaturesoderoptionalFeaturesisthandimmernull. Nutze besseroptionalFeatures, damit die Sitzung auch auf Geräten ohne Hand-Tracking startet. - Immer auf
nullprüfen: Nutzer können jederzeit zwischen Controllern und Händen wechseln. Dabei feuertinputsourceschange, und es entstehen neueXRInputSource-Objekte. Halte daher keine veralteten Referenzen fest. - Posen können fehlen:
getJointPose()gibtundefinedzurück, wenn ein Gelenk verdeckt ist oder nicht getrackt wird.fillPoses()gibt in diesem Fallfalsezurück. - Leistung: Wenn du pro Frame alle 25 Gelenke brauchst, verwende
fillPoses()statt 25 einzelner Aufrufe vongetJointPose(). - 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.