2026 Spatial ComputingSafari & Web
WWDC26 · 16 min · Spatial Computing / Safari & Web
Get started with the HTML Model Element
Learn how the model element brings interactive 3D content to your websites — now on iOS, iPadOS, macOS, and visionOS. Discover tools for creating and optimizing 3D assets. Explore model element’s features and see how web standards are shaping the future of 3D on the web.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 10 snippets
Load a model
<!-- Using the src attribute -->
<model src="mallet.usdz"></model>
<!-- Using a <source> child for MIME type -->
<model>
<source src="mallet.usdz" type="model/vnd.usdz+zip">
</model> Image fallback
<model id="mallet" src="mallet.usdz">
<img src="mallet.png"
alt="Rubber mallet with wooden handle">
</model> Ready promise
<model id="mallet" src="mallet.usdz"></model>
<script>
const model = document.getElementById("mallet");
model.ready.then(result => {
// Hide the loading indicator
}).catch(error => {
// Loading failed, show fallback
});
</script> Polyfill fallback
<script type="module">
if (!window.HTMLModelElement) {
import("model-element-polyfill.js").then(() => {
// Polyfill ready to use
});
}
</script> Model background
<model id="mallet" src="mallet.usdz"></model>
<style>
model {
background-color: #f4f1ec;
}
</style> Stage mode
<model id="mallet"
src="mallet.usdz"
stagemode="orbit">
</model> Custom transforms
<model id="boot" src="boot.usdz"></model>
<button id="button-side">Side</button>
<button id="button-reset">Reset</button>
<script>
const model = document.getElementById("boot");
const initialTransform = model.entityTransform;
document.getElementById("button-side")
.addEventListener("click", () => {
const transform = new DOMMatrix();
transform.rotateSelf(0, 135, 0);
model.entityTransform = transform;
});
document.getElementById("button-reset")
.addEventListener("click", () => {
model.entityTransform = initialTransform;
});
</script> Transition animation
<script>
const model = document.getElementById("boot");
const duration = 500;
let currentAngle = 0;
let animationId = null;
function animateTo(targetAngle) {
if (animationId) cancelAnimationFrame(animationId);
const startAngle = currentAngle;
const startTime = performance.now();
function step(now) {
const progress = Math.min((now - startTime) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
currentAngle = startAngle + (targetAngle - startAngle) * ease;
model.entityTransform = new DOMMatrix().rotateSelf(0, currentAngle, 0);
if (progress < 1) animationId = requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
document.getElementById("button-side").addEventListener("click", () => animateTo(135));
document.getElementById("button-reset").addEventListener("click", () => animateTo(0));
</script> Animation playback
<model id="bottle" src="bottle.usdz"></model>
<button id="button-play" onclick="play(5)">
Play
</button>
<button id="button-reverse" onclick="play(-5)">
Reverse
</button>
<script>
const model = document.getElementById("bottle");
function play(rate) {
model.playbackRate = rate;
model.play();
}
</script> AR Quick Look
<a rel="ar" href="bottle.usdz">
<model id="boot" src="bottle.usdz"></model>
</a> Resources
Related sessions
-
19 min -
9 min -
29 min -
17 min