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

2025 Spatial ComputingSafari & Web

WWDC25 · 29 min · Spatial Computing / Safari & Web

What’s new for the spatial web

Discover the latest spatial features for the web on visionOS 26. We’ll cover how to display inline 3D models with the brand new HTML model element. And we’ll share powerful features, including model lighting, interactions, and animations. Learn how to embed newly supported immersive media on your web site, such as 360-degree video and Apple Immersive Video. And get a sneak peek at adding a custom environment to your web pages.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 24 snippets

Embed 3D models - Basic syntax xml · at 1:00 ↗
<model src="teapot.usdz"></model>
Embed 3D models with source element xml · at 4:15 ↗
<model>
  <source src="teapot.usdz" type="model/vnd.usdz+zip">
</model>
Example server configurations to add USDZ MIME type support markdown · at 5:30 ↗
# Apache

```
AddType model/vnd.usdz+zip .usdz
```

# NGINX mime.types

```
types {
  ...
  model/vnd.usdz+zip usdz;
}
```

# Python HTTP server

```
import http.server
Handler = http.server.SimpleHTTPRequestHandle
Handler.extensions_map = { ".usdz": "model/vnd.usdz+zip" }
httpd = http.server.HTTPServer(("", 8000), Handler)
httpd.serve_forever()
```
Specify a fall back image for <model> element xml · at 5:51 ↗
<model src="camera.usdz">
  <img src="camera.png">
</model>
Example 2D rendering fallback experience xml · at 6:17 ↗
<!-- <model-viewer> library from https://modelviewer.dev/ -->
<script type="module" 
  src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js">
</script>

<model src="camera.usdz">
  <!-- Fallback experience for backward compatibility -->  
  <model-viewer src="camera.glb"></model-viewer>
</model>
Detect if the model element is supported javascript · at 6:52 ↗
if (window.HTMLModelElement) {
  // Supported by this browser
} else {
  // Not supported by this browser
}
Implementing a loading indicator using .ready promise xml · at 7:32 ↗
<model src="camera.usdz" id="mymodel"></model>

<script>
const mymodel = document.getElementById("mymodel");

if (window.HTMLModelElement) {
  mymodel.ready.then(result => {
	// Hide the loading indicator
	// Show the model
 }).catch(error => {
	// Loading error occurred, show a retry button
 });
}
</script>
CSS example for setting the color of the virtual space xml · at 8:23 ↗
<body>
  <!-- page content here -->
  <model src="camera.usdz" class="my_model"></model>
</body>

<style>
:root {
  --main-bg-color: rgb(240, 240, 240);
}

body {
  background-color: var(--main-bg-color);
}

.my_model {
  /* set the virtual space color */
  background-color: var(--main-bg-color); 
}
</style>
CSS example for frosted glass panel on top of a <model> xml · at 9:21 ↗
<div class="container">
  <model src="camera.usdz"></model>
  <div class="panel"> ... </div>
</div>

<style>
.container {
  position: relative;
}

.panel {
  position: absolute;
  left: 60%;
  backdrop-filter: blur(20px);
  background: linear-gradient(to right,
                              rgba(240, 240, 240, 0.8),
                              rgba(240, 240, 240, 0.5) 4px);
}
</style>
Setting image-based lighting (IBL) with environmentmap xml · at 10:56 ↗
<model src="camera.usdz" environmentmap="sunset.exr"></model>
Allowing inline rotation with stagemode xml · at 12:41 ↗
<model src="teapot.usdz" stagemode="orbit"></model>
Customize placement with JavaScript entityTransform xml · at 13:31 ↗
<model src="teapot.usdz" id="mymodel"></model>

<script>
const mymodel = document.getElementById("mymodel");
mymodel.ready.then(result => {
  const matrix = mymodel.entityTransform; // DOMMatrixReadOnly
});
</script>
Make the model face right with entityTransform xml · at 13:49 ↗
<model src="teapot.usdz" id="mymodel"></model>
<a onclick="turnRight()">Right</a>

<script>
const mymodel = document.getElementById("mymodel");
function turnRight() {
  const matrix = mymodel.entityTransform; // DOMMatrixReadOnly
  const newMatrix = matrix.rotateAxisAngle(0, 1, 0, 90);
  mymodel.entityTransform = newMatrix;
}
</script>
Setting the entityTransform to an identity matrix javascript · at 15:03 ↗
model.entityTransform = new DOMMatrix();
Basic animation control xml · at 16:31 ↗
<model src="toy.usdz" id="mymodel" loop autoplay></model>
<button onclick="toggleAnimation()">Play/Pause</button>

<script>
const mymodel = document.getElementById("mymodel");

function toggleAnimation() {
  if (mymodel.paused) {
	mymodel.play();
  } else {
	mymodel.pause();
  }
}
</script>
Jump to animation timestamp using .currentTime property xml · at 17:35 ↗
<model src="camera.usdz" id="mymodel"></model>

<script>
const mymodel = document.getElementById("mymodel");

function openFlash() {
  mymodel.currentTime = 1; // Unit is seconds
}

function openScreen() {
  mymodel.currentTime = 3; // Unit is seconds
}
</script>
Update .currentTime with a slider xml · at 18:11 ↗
<model src="camera.usdz" id="mymodel"></model>
<input type="range" id="slider" min="2" max="3" step="any" value="2">


<script>
const mymodel = document.getElementById("mymodel");

slider.addEventListener("input", (event) => {
  mymodel.currentTime = event.target.value;
});
</script>
Generate USDZ with three.js and display with <model> javascript · at 19:35 ↗
import * as THREE from "three";
import { USDZExporter } from "three/examples/exporters/USDZExporter.js";

async function generateModel() {
	const scene = new THREE.Scene();
	// ... create a really nice scene procedurally ...

	const bytes = await new USDZExporter().parseAsync(scene);
	const objURL = URL.createObjectURL(new Blob([bytes]));

	const mymodel = document.getElementById("mymodel");
	mymodel.setAttribute("src", objURL);
}
Embed immersive media xml · at 23:10 ↗
<video src="spatial_video.mov"></video>  <!-- Single file -->
<video src="360_video.m3u8"></video>  <!-- HTTP Live Streaming -->
Going full screen with Javascript for <video> elements xml · at 24:25 ↗
<video src="360_video.m3u8" id="player" controls></video>

<script>
const player = document.getElementById("player");
player.requestFullScreen();
</script>
Embed panoramas and offer full screen with Javascript xml · at 24:35 ↗
<picture>
  <source media="(max-width: 799px)" srcset="thumbnail.jpg">
  <source media="(min-width: 800px)" srcset="panorama.jpg">
  <img src="panorama.jpg" id="pano">
</picture>
    
<script>
const pano = document.getElementById("pano");
pano.requestFullScreen();
</script>
Embed spatial photos and offer full screen with Javascript xml · at 24:57 ↗
<img src="spatial.heic" id="img">
  
<script>
const img = document.getElementById("img");
img.requestFullScreen();
</script>
Embed spatial photos with the new "controls" attribute xml · at 25:21 ↗
<img src="spatial.heic" id="img" controls>
Provide a custom environment xml · at 26:49 ↗
<link rel="spatial-backdrop" href="office.usdz" environmentmap="lighting.hdr">

Resources