WebXR is the browser standard for immersive VR and AR. It handles head
tracking, stereo rendering, controller poses, and haptics — all from
JavaScript. This tutorial walks through entering an inline preview
session, upgrading to immersive-vr, handling controller input, and
rendering a simple interactive VR scene with Three.js.
1Check XR support and show the button
const btn = document.getElementById('vr-btn'); if (!navigator.xr) {
btn.textContent = 'WebXR not supported'; btn.disabled = true; } else {
// Check if immersive-vr is available (requires HTTPS + headset)
navigator.xr.isSessionSupported('immersive-vr').then(supported => { if
(supported) { btn.textContent = 'Enter VR';
btn.addEventListener('click', startXR); } else { btn.textContent = 'VR
not available on this device'; btn.disabled = true; } }); } // Always
show inline 3D preview (works on any device)
navigator.xr?.isSessionSupported('inline').then(ok => { if (ok)
startInlineSession(); });
WebXR requires HTTPS (or localhost). Use
serve / vite locally — plain
file:// URLs won't work.
2Start an immersive-vr session
import * as THREE from
'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.xr.enabled = true;
document.body.appendChild(renderer.domElement); let xrSession = null;
async function startXR() { xrSession = await
navigator.xr.requestSession('immersive-vr', { requiredFeatures:
['local-floor'], // floor-relative reference space optionalFeatures:
['hand-tracking'], // hand joints if available }); // Tell Three.js
about the session — it handles stereo rendering internally await
renderer.xr.setSession(xrSession); // Session ends when user removes
headset xrSession.addEventListener('end', () => { xrSession = null;
btn.textContent = 'Enter VR'; }); btn.textContent = 'Exit VR'; }
3The XR render loop
In XR mode, Three.js takes over requestAnimationFrame.
Use renderer.setAnimationLoop — it works both in-page and
in VR:
const scene = new THREE.Scene(); const camera = new
THREE.PerspectiveCamera(70, innerWidth/innerHeight, 0.01, 100);
scene.add(camera); // Add some objects to the scene const geo = new
THREE.BoxGeometry(0.2, 0.2, 0.2); const mat = new
THREE.MeshStandardMaterial({ color: 0x22c55e }); for (let i = 0; i
< 10; i++) { const box = new THREE.Mesh(geo, mat);
box.position.set( (Math.random() - 0.5) * 4, Math.random() * 1.5, -1 -
Math.random() * 3 ); scene.add(box); } // Add light scene.add(new
THREE.AmbientLight(0xffffff, 0.5)); const dirLight = new
THREE.DirectionalLight(0xffffff, 1); dirLight.position.set(2, 4, 2);
scene.add(dirLight); // The loop — called by the browser at 90 Hz in
VR renderer.setAnimationLoop((time, frame) => { renderer.render(scene,
camera); });
The frame argument is an XRFrame — use it to
query controller poses, hand joints, and AR hit tests in the same
loop.
4Read controller poses
// Three.js XRControllerModelFactory handles controller meshes
automatically import { XRControllerModelFactory } from
'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/webxr/XRControllerModelFactory.js';
const cmf = new XRControllerModelFactory(); const controllers = [0,
1].map(i => { // Grip space: physical controller model const grip =
renderer.xr.getControllerGrip(i);
grip.add(cmf.createControllerModel(grip)); scene.add(grip); // Ray
space: pointing ray const ctrl = renderer.xr.getController(i);
ctrl.addEventListener('selectstart', onSelectStart);
ctrl.addEventListener('selectend', onSelectEnd); scene.add(ctrl); //
Visualise ray const ray = new THREE.Line( new
THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new
THREE.Vector3(0, 0, -1), ]), new THREE.LineBasicMaterial({ color:
0x22c55e }) ); ctrl.add(ray); return ctrl; }); function
onSelectStart(e) { // Controller pressed trigger const ctrl =
e.target; console.log('Select start from', ctrl === controllers[0] ?
'left' : 'right'); }
Haptics require a brief pulse < 250 ms — longer pulses are silently
ignored by most runtimes. The intensity range is 0–1.
Frequently Asked Questions
What will I learn in this tutorial?
Enter VR and AR from the browser using the WebXR Device API — set up an XR session, handle controller input, and render a stereo 3D scene.
What topics are covered in this tutorial?
This tutorial covers: Check XR support and show the button, Start an immersive-vr session, The XR render loop, Read controller poses, Gamepad buttons and haptics.
What tools and technologies does this tutorial use?
This tutorial uses WebXR, Three.js, VR, Controller Input.
How long does this tutorial take?
This tutorial takes approximately 50 minutes to complete.
What prerequisites do I need before starting?
This is a Intermediate-level tutorial — no special preparation beyond basic JavaScript is assumed.