-
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.
Chapters
- 0:00 - Introduction
- 1:46 - Meet the immersive API
- 4:16 - Preview environments inline
- 7:01 - Go immersive
- 12:04 - Optimize the experience
- 17:17 - Image controls
- 18:09 - Next steps
Resources
- Download - Immersive model add-on for Blender
- WebKit.org - Theater Ticket Sales immersive website environment demo for Apple Vision Pro
- WebKit.org - Escape Game immersive website demo for Apple Vision Pro
- GitHub: Spatial Backdrop explainer
- WebKit.org – Report issues to the WebKit open-source project
- Submit feedback
Related Videos
WWDC26
- Design immersive environments for visionOS apps and the spatial web
- Get started with the HTML Model Element
- What’s new in WebKit for Safari 27
WWDC25
-
Search this video…
-
-
1:51 - Basic model element
<model src="teapot.usdz"> </model> -
2:06 - Model element with environment map
<model src="teapot.usdz" environmentmap="kitchen.hdr"> </model> -
4:40 - Adding the environment model on the page for inline preview
<div class="seat-preview"> <model id="theater" src="theater-model.usdz" environmentmap="theater-lighting.hdr"> </model> </div> -
5:14 - Reset the model entity transform
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(); -
5:42 - Translate the model down
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(); -
6:40 - Build the seat transform
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; } -
7:16 - Detect feature availability
if (document.immersiveEnabled) { immersiveButton.hidden = false; } -
7:34 - Request the immersive transition on the model
immersiveButton.addEventListener("click", async () => { await model.requestImmersive(); }); -
8:24 - Build immersive transform
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; } -
9:01 - Update the entity transform and the layout on immersive state updates
theater.addEventListener("immersivechange", () => { const isImmersive = !!document.immersiveElement; const transform = buildTransform(isImmersive, currentSeat); theater.entityTransform = transform; document.body.classList.toggle("immersive", isImmersive); }); -
10:53 - Hide the inline preview
<model id="escapeRoom" src="escape-room.usdz" environmentmap="room-lighting.hdr" style="display: none"> </model> -
11:25 - Request an immersive transition on the escape room model
const enterButton = document.getElementById("enterButton"); const escapeRoom = document.getElementById("escapeRoom"); enterButton.addEventListener("click", () => { await escapeRoom.requestImmersive(); }); -
11:52 - Handle the request result and show a loading animation
enterButton.addEventListener("click", async () => { showLoadingAnimation(); try { await escapeRoom.requestImmersive(); } catch (error) { console.log(error); } finally { hideLoadingAnimation(); } }); -
13:16 - Dock the video in the environment with the fullscreen API
const trailerVideo = document.getElementById("trailerVideo"); const demoButton = document.getElementById("demoButton"); demoButton.addEventListener("click", async () => { await trailerVideo.requestFullscreen(); }); -
14:01 - Play the model animation
const trailerVideo = document.getElementById("trailerVideo"); const escapeRoom = document.getElementById("escapeRoom"); trailerVideo.addEventListener("ended", async () => { await document.exitFullscreen(); escapeRoom.play(); }); -
16:38 - Compress your USDZ with usdcrush
usdcrush model.usdz -o optimized.usdz
-
-
- 0:00 - Introduction
The immersive API in visionOS Safari is previewed through two example websites — a theater ticket sales experience and an escape-room marketing site — that transport visitors into virtual environments using just a few lines of code.
- 1:46 - Meet the immersive API
Get a high-level overview of how the HTML `
` element pairs with the new JavaScript `requestImmersive()` API and a `:immersive` CSS pseudo-class. Unlike the Fullscreen API, the immersive API opens an environment around your existing webpage rather than replacing its content. - 4:16 - Preview environments inline
Build the inline portion of the ticket sales site: load a theater model into the page, let visitors pick a seat by applying a `DOMMatrix` transform to the `
` element, and prepare the same model for an immersive transition. - 7:01 - Go immersive
Transition from the inline preview into a full immersive environment. Covers the difference between inline and immersive coordinate systems, listening to `immersivechange` events, dismissing the environment, and skipping the inline preview entirely for the escape-room marketing site.
- 12:04 - Optimize the experience
Polish your environment with RealityKit annotations authored in Reality Composer Pro or via a Blender plugin. Dock playing video into a TV inside the scene, trigger model animations from JavaScript, cast Safari's window shadow with the Scene Understanding component, and reduce vertex/entity counts to keep assets fast to load and render.
- 17:17 - Image controls
Add a single `controls` attribute to an `
` element to give visitors an immersive viewing affordance for spatial photos — a small markup change that pairs naturally with model-based environments.
- 18:09 - Next steps
Try the immersive demos on webkit.org with an Apple Vision Pro, file feedback at bugs.webkit.org, and watch "Design immersive environments for visionOS apps and the spatial web" for the design principles behind great photorealistic environments.