WebXR: A first look at immersive browsing
The web platform crossed a threshold with Chrome 79, which shipped the WebXR Device API and brought virtual reality to the browser. Chrome 81 extends that support to augmented reality, and an update to the GamePad API expands the use of controls in VR. Other browsers including Firefox Reality, Oculus Browser, Edge and Magic Leap's Helio browser are expected to follow with support.
This article opens a series on the immersive web, covering the basics of building a WebXR application: setting up a session, entering it, and understanding the structure of the frame loop. Later installments will examine the frame loop in detail, the specifics of AR, and the WebXR Hit Test API for detecting surfaces in AR sessions.
Understanding the immersive spectrum
The terms augmented reality and virtual reality are commonly used to describe immersive experiences, but it's more accurate to think of them as points on a continuum from complete reality to fully virtual environments, with varying degrees of immersion in between. The "X" in XR acts as a variable representing any point along this spectrum.
Practical applications of immersive experiences include:
- Games
- 360° videos
- Traditional videos presented within immersive surroundings
- Home buying and property tours
- Previewing products in your own space before purchasing
- Immersive art installations
The WebXR Device API specification has been developed alongside security and privacy protections for users. Implementations must follow particular rules: a page must be active and focused before it can request sensitive data, and it must be served over HTTPS. The API is also designed to safeguard the sensor and camera data it depends on.
Requesting a session
Starting an XR session requires a user gesture. Feature detection is the first step: check for XRSystem via navigator.xr and then call XRSystem.isSessionSupported(). You'll request a session type like 'immersive-vr' or 'immersive-ar'. There's also 'inline' for displaying content directly in HTML, typically used for teasers.
Once support for the desired session type is confirmed, you can enable a button that will capture the user gesture.
if (navigator.xr) {
const supported = await navigator.xr.isSessionSupported('immersive-vr');
if (supported) {
xrButton.addEventListener('click', onButtonClicked);
xrButton.textContent = 'Enter VR';
xrButton.enabled = supported; // supported is Boolean
}
}
With the button enabled, a click event triggers the session request.
let xrSession = null;
function onButtonClicked() {
if (!xrSession) {
navigator.xr.requestSession('immersive-vr')
.then((session) => {
xrSession = session;
xrButton.textContent = 'Exit XR';
onSessionStarted(xrSession);
});
} else {
xrSession.end();
}
}
Note the object hierarchy: the path moves from navigator to xr to an XRSession instance. Older versions of the API required an explicit device request before creating a session; now the device is selected implicitly.
Entering a session
Before the session can begin, a few elements need to be in place. A session requires an onend event handler to reset the app when the user exits. You'll also need a <canvas> element set up with an XR-compatible WebGLRenderingContext or WebGL2RenderingContext, or a framework like Three.js built on top of them.
With the canvas ready, you create an XRWebGLLayer to link it to the session and register it via XRSession.updateRenderState(). You also need a reference space to determine the position of objects in virtual reality. A 'local-floor' reference space sets the origin near the viewer with the y-axis at floor level, and is suitable for many applications. You'll want to keep a reference to it for rendering frames.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
let canvas = document.createElement('canvas');
webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
});
xrSession.requestReferenceSpace('local-floor')
.then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
After acquiring the reference space, you call XRSession.requestAnimationFrame() to begin the frame loop, where content is continually presented to the display.
How the frame loop works
The frame loop is an infinite, user-agent controlled cycle that repeatedly draws content to the screen in discrete blocks called frames. The succession of these frames produces the illusion of motion. Frame rates can vary significantly — from 60 to 144 FPS in VR applications on certain hardware, down to 30 FPS for AR on Android. Code should not assume a specific frame rate.
The core process involves these steps:
- Call
XRSession.requestAnimationFrame(), which causes the user agent to invoke your callback function. - Within that callback:
- Call
XRSession.requestAnimationFrame()again to schedule the next frame. - Retrieve the viewer's current pose.
- Bind the
WebGLFramebufferfrom theXRWebGLLayerto theWebGLRenderingContext. - For each
XRView, get itsXRViewportfrom theXRWebGLLayerand apply it to theWebGLRenderingContext. - Render the scene to the framebuffer.
- Call
The remainder of this article focuses on the first step and the setup for the callback; the rendering details within the callback will be covered in the next part of this series.
Your callback function
The XRFrameRequestCallback is defined by you and accepts two arguments: a DOMHighResTimeStamp (currently reserved for future use) and an XRFrame object, which contains the data needed to render a single frame.
As a good habit, request the next animation frame at the very top of the callback. Frame timing is managed by the user agent based on the underlying hardware, and requesting the next frame first ensures the loop keeps running even if an error occurs later in the callback.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
// Render a frame.
}
Ending an immersive session
A session can finish in several ways. Your own code can terminate it with XRSession.end(), or external events may interrupt it — such as a disconnected headset or another application taking over the hardware. A well-behaved application should always monitor the end event. When it fires, clean up the session and its associated rendering objects. Note that an ended immersive session cannot be resumed; re-entering requires starting an entirely new session.
During setup, an onend event handler was added to the session.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
// More setup…
}
Inside that handler, restores the state of the application to how it was before the user entered the immersive experience.
function onSessionEnded(event) {
xrSession = null;
xrButton.textContent = 'Enter VR';
}
What's next
This introduction doesn't cover everything needed to build a WebXR application, but it should give you enough context to parse existing code and start experimenting on your own. The next article will take an in-depth look at the frame loop, where the actual drawing takes place.



