Signatur
Beschreibung
XRSession.requestAnimationFrame() ist das Gegenstück zu window.requestAnimationFrame() für immersive VR- und AR-Sitzungen. Sie registriert eine Funktion, die der Browser genau einmal aufruft, wenn das XR-Gerät den nächsten Frame anfordert. Der Takt richtet sich dabei nach der Bildwiederholrate des Headsets, also z. B. 72, 90 oder 120 Hz, und nicht nach der des Monitors.
Der Rückruf bekommt zwei Argumente: einen hochauflösenden Zeitstempel (DOMHighResTimeStamp) und ein XRFrame. Nur über dieses XRFrame kommst du an die aktuelle Pose des Betrachters (frame.getViewerPose()), an die Posen der Eingabegeräte (frame.getPose()), an Hit-Test-Ergebnisse und an Anker. Das Objekt ist nur während des Rückrufs gültig.
Wie bei der Window-Variante gilt auch hier: Der Rückruf läuft nur einmal. Für eine durchgehende Render-Schleife musst du darin erneut session.requestAnimationFrame() aufrufen. Gerendert wird in den Framebuffer des XRWebGLLayer, der über session.updateRenderState({ baseLayer }) gesetzt wurde. Mit XRSession.cancelAnimationFrame() kannst du einen geplanten Rückruf wieder abbrechen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $callback Pflicht | (time: DOMHighResTimeStamp, frame: XRFrame) => void | Funktion, die vor dem nächsten XR-Frame aufgerufen wird. time ist der Zeitpunkt des Frames in Millisekunden, frame ist das XRFrame mit Posen- und Zustandsdaten für genau diesen Frame. |
Rückgabewert
XRSession.cancelAnimationFrame() übergeben, um den Rückruf abzubrechen.Beispiele
Grundlegende Render-Schleife einer VR-Sitzung
const startXR = async () => {
if (!navigator.xr || !(await navigator.xr.isSessionSupported('immersive-vr'))) {
console.log('immersive-vr wird nicht unterstützt');
return;
}
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2', { xrCompatible: true });
const session = await navigator.xr.requestSession('immersive-vr');
session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
const refSpace = await session.requestReferenceSpace('local');
const onXRFrame = (time, frame) => {
// Nächsten Frame sofort wieder anfordern
frame.session.requestAnimationFrame(onXRFrame);
const pose = frame.getViewerPose(refSpace);
if (!pose) return; // Tracking verloren
const layer = frame.session.renderState.baseLayer;
gl.bindFramebuffer(gl.FRAMEBUFFER, layer.framebuffer);
gl.clearColor(0.1, 0.1, 0.2, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
for (const view of pose.views) {
const vp = layer.getViewport(view);
gl.viewport(vp.x, vp.y, vp.width, vp.height);
// Hier die Szene mit view.projectionMatrix und view.transform.inverse.matrix zeichnen
}
};
session.requestAnimationFrame(onXRFrame);
};
// Muss durch eine Nutzergeste ausgelöst werden
document.querySelector('#enter-vr').addEventListener('click', startXR);
Zeitbasierte Animation mit Delta-Zeit
let lastTime = null;
let angle = 0;
const onXRFrame = (time, frame) => {
frame.session.requestAnimationFrame(onXRFrame);
// Delta in Sekunden, unabhängig von 72, 90 oder 120 Hz
const dt = lastTime === null ? 0 : (time - lastTime) / 1000;
lastTime = time;
angle += dt * Math.PI / 2; // 90° pro Sekunde
// ... Objekt mit 'angle' rotieren und zeichnen
};
xrSession.requestAnimationFrame(onXRFrame);
Render-Schleife pausieren und abbrechen
let frameId = 0;
let paused = false;
const onXRFrame = (time, frame) => {
frameId = frame.session.requestAnimationFrame(onXRFrame);
// ... zeichnen
};
const pause = () => {
xrSession.cancelAnimationFrame(frameId);
paused = true;
};
const resume = () => {
if (!paused) return;
paused = false;
frameId = xrSession.requestAnimationFrame(onXRFrame);
};
frameId = xrSession.requestAnimationFrame(onXRFrame);
xrSession.addEventListener('end', () => {
console.log('Sitzung beendet, Schleife stoppt automatisch');
});
// Wichtig · Fallstricke
window.requestAnimationFrame()nicht verwenden: Solange eine immersive Sitzung aktiv ist, liefert die Window-Variante keine Posen. Außerdem kann sie gedrosselt oder ganz angehalten werden. Für XR-Inhalte immersession.requestAnimationFrame()nutzen.XRFramenur im Rückruf gültig: Speicherst du das Frame-Objekt und rufst später z. B.getViewerPose()auf, wird einInvalidStateErrorgeworfen.- Kein Bild ohne Layer: Ist über
updateRenderState()keinbaseLayergesetzt, zeigt das Gerät nichts an. Außerdem müssen alle Zeichenbefehle inlayer.framebuffergehen, nicht in den Standard-Framebuffer. getViewerPose()kannnullliefern: Das passiert, wenn das Tracking verloren geht. Diesen Fall abfangen und trotzdem den nächsten Frame anfordern, sonst bleibt die Schleife stehen.- Nach dem Ende der Sitzung: Wenn die Sitzung endet (
end-Event), werden keine weiteren Rückrufe mehr ausgeführt. Neue Aufrufe haben dann keine Wirkung. - Leistung: Das Frame-Budget ist knapp (bei 90 Hz etwa 11 ms). Lange Berechnungen im Rückruf führen zu Rucklern und damit oft zu Übelkeit beim Nutzer. Aufwendige Arbeit gehört in Worker oder muss auf mehrere Frames verteilt werden.
- Kompatibilität: WebXR steht nur in sicheren Kontexten (HTTPS) zur Verfügung. Die Unterstützung ist gut in Chromium-Browsern und im Meta Quest Browser, Firefox und Safari (Desktop) unterstützen WebXR nicht. Deshalb vorher mit
navigator.xr?.isSessionSupported()prüfen.