Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2026 Spatial ComputingSafari & Web

WWDC26 · 19 min · Spatial Computing / Safari & Web

Explore immersive website environments in visionOS

Transport your website’s visitors into virtual environments in Apple Vision Pro using the new Immersive API in JavaScript. Explore how to request immersive transitions from an inline model element, create compelling immersive experiences using features like video docking, and optimize performance for rich, real-world-scale experiences — all with just a few lines of code running on your website.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 16 snippets

Basic model element xml · at 1:51 ↗
<model src="teapot.usdz">
</model>
Model element with environment map xml · at 2:06 ↗
<model src="teapot.usdz"
	environmentmap="kitchen.hdr">
</model>
Adding the environment model on the page for inline preview xml · at 4:40 ↗
<div class="seat-preview">
	<model id="theater"
		   src="theater-model.usdz"
		   environmentmap="theater-lighting.hdr">
	</model>
</div>
Reset the model entity transform javascript · at 5:14 ↗
const theater = document.getElementById("theater");

async function updateModelTransform() {
	// Make sure the model is loaded
	await theater.ready;
	// Create a transform matrix
	const identity = new DOMMatrix();
	// Apply the transform matrix to the model
	theater.entityTransform = identity;
}

updateModelTransform();
Translate the model down javascript · at 5:42 ↗
const theater = document.getElementById("theater");

async function updateModelTransform() {
	// Make sure the model is loaded
	await theater.ready;
	// Create a transform matrix
	const transform = new DOMMatrix();
	// Translate model down, for eye level preview
	transform.translateSelf(
		0, 			// x
		-1.0, 	// y
		0 			// z
	);
	// Apply the transform matrix to the model
	theater.entityTransform = transform;
}

updateModelTransform();
Build the seat transform javascript · at 6:40 ↗
function buildTransform(seat) {
	const transform = new DOMMatrix();
	const { x, y, z, ry } = seat;
	// Rotate and translate the model to match 
  // the seat's origin and orientation
	transform.rotateSelf(0, -ry, 0);
	transform.translateSelf(-x, -y, -z);
	// Translate the model down, for eye level preview
	transform.translateSelf(0, -1.0, 0);
	return transform;
}
Detect feature availability javascript · at 7:16 ↗
if (document.immersiveEnabled) {
	immersiveButton.hidden = false;
}
Request the immersive transition on the model javascript · at 7:34 ↗
immersiveButton.addEventListener("click", async () => {
	await model.requestImmersive();
});
Build immersive transform javascript · at 8:24 ↗
function buildTransform(seat, immersive) {
	const transform = new DOMMatrix();
	// [...] Seat transform logic
	if (immersive) {
		// Rotate to the left
		transform.rotateSelf(
			0,		// x
			45,		// y
			0			// z
		);
	} else {
		// [...] Eye level translation
	}
	return transform;
}
Update the entity transform and the layout on immersive state updates javascript · at 9:01 ↗
theater.addEventListener("immersivechange", () => {
	const isImmersive = !!document.immersiveElement;
	const transform = buildTransform(isImmersive, currentSeat);
	theater.entityTransform = transform;
  document.body.classList.toggle("immersive", isImmersive);
});
Hide the inline preview xml · at 10:53 ↗
<model id="escapeRoom"
	   src="escape-room.usdz"
	   environmentmap="room-lighting.hdr"
	   style="display: none">
</model>
Request an immersive transition on the escape room model javascript · at 11:25 ↗
const enterButton = document.getElementById("enterButton");
const escapeRoom = document.getElementById("escapeRoom");

enterButton.addEventListener("click", () => {
    await escapeRoom.requestImmersive();
});
Handle the request result and show a loading animation javascript · at 11:52 ↗
enterButton.addEventListener("click", async () => {
	showLoadingAnimation();            
	try {
		await escapeRoom.requestImmersive();
	} catch (error) {
		console.log(error);
	} finally {
		hideLoadingAnimation();
	}
});
Dock the video in the environment with the fullscreen API javascript · at 13:16 ↗
const trailerVideo = document.getElementById("trailerVideo");
const demoButton = document.getElementById("demoButton");

demoButton.addEventListener("click", async () => {
	await trailerVideo.requestFullscreen();
});
Play the model animation javascript · at 14:01 ↗
const trailerVideo = document.getElementById("trailerVideo");
const escapeRoom = document.getElementById("escapeRoom");

trailerVideo.addEventListener("ended", async () => {
	await document.exitFullscreen();
	escapeRoom.play();
});
Compress your USDZ with usdcrush bash · at 16:38 ↗
usdcrush model.usdz -o optimized.usdz

Resources