Start · Sprachen · JavaScript · Referenz · XRSession.requestAnimationFrame()

XRSession.requestAnimationFrame()

Methode

Plant einen Rückruf, der aufgerufen wird, sobald der Browser bereit ist, den nächsten Frame der XR-Sitzung zu zeichnen. Die Methode gibt eine Kennung zum Abbrechen zurück.

seit JavaScript WebXR Device API – Chromium-Browser (Chrome/Edge 7 Kategorie: webapi

Signatur

XRSession.prototype.requestAnimationFrame(callback)

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

Typ
number
Beschreibung
Eine ganze Zahl ungleich null, die den geplanten Rückruf eindeutig kennzeichnet. Du kannst sie an 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');
});
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 immer session.requestAnimationFrame() nutzen.
  • XRFrame nur im Rückruf gültig: Speicherst du das Frame-Objekt und rufst später z. B. getViewerPose() auf, wird ein InvalidStateError geworfen.
  • Kein Bild ohne Layer: Ist über updateRenderState() kein baseLayer gesetzt, zeigt das Gerät nichts an. Außerdem müssen alle Zeichenbefehle in layer.framebuffer gehen, nicht in den Standard-Framebuffer.
  • getViewerPose() kann null liefern: 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.