Three.js From Zero · Article s11-03
S11-03 AR View
AR View — Quick Look + Scene Viewer + WebXR
The end-to-end AR pipeline. Detect device → choose route. iOS gets USDZ via <a rel="ar">. Android gets Scene Viewer via intent URL. Capable web browsers get WebXR. Desktop gets a QR fallback. One button, four code paths, all live in this article.
1. Why AR is a separate article
AR is not a single feature. It is three different platforms behind one button — and a fourth, the desktop fallback, behind the scenes:
- iOS Safari → AR Quick Look. A USDZ file linked with
<a rel="ar">. Apple owns the player. No JS required. - Android Chrome → Scene Viewer. A glTF / glb served over HTTPS, opened with an
intent://URL. Google owns the player. - WebXR (Chrome / Quest / Vision Pro browser). An in-browser AR session via
navigator.xr.requestSession('immersive-ar'). You own the player. Three.js renders. - Desktop / unsupported. Show a QR code that points to the mobile URL. The "scan with your phone" pattern.
Production configurators ship all four. The detect-and-route logic is twenty lines. The asset pipeline (glTF → USDZ; the right glTF for Scene Viewer) is the part that takes a week to get right.
<model> HTML element. It will eventually disintermediate parts of this pipeline for simple use cases. Until adoption is deep, the four-path approach below remains correct.2. Platform detection (the routing layer)
Detection is feature-based, not user-agent-based — except where Apple makes us read the UA. The check order matters: iOS first (USDZ wins), Android second (intent URL wins), WebXR third, fallback last.
async function pickARRoute() {
const ua = navigator.userAgent;
// 1. iOS — Quick Look
const isIOS = /iPad|iPhone|iPod/.test(ua) ||
(ua.includes('Mac') && navigator.maxTouchPoints > 1);
if (isIOS && document.createElement('a').relList.supports('ar')) {
return 'quicklook';
}
// 2. Android — Scene Viewer (Chrome only ships the intent reliably)
const isAndroid = /Android/i.test(ua);
if (isAndroid) return 'sceneviewer';
// 3. WebXR — capable in-browser
if ('xr' in navigator) {
try {
const ok = await navigator.xr.isSessionSupported('immersive-ar');
if (ok) return 'webxr';
} catch {}
}
// 4. Fallback — show a QR pointing to the same page on mobile
return 'qr';
}
3. iOS — AR Quick Look (the easy one)
An anchor tag with rel="ar" and an .usdz href. Safari does the rest.
<a rel="ar" href="/models/chair.usdz">
<img src="chair.png" alt="View in your space" />
</a>
Two ways to get a USDZ:
- Reality Composer Pro on macOS. Drag your glTF in, export USDZ. Hand-curates animations and behaviors. The Apple-recommended pipeline.
- gltf-transform usd on the CLI:
gltf-transform usd in.glb out.usdz. Open source, automatable, ships in CI. Slightly less control over animation triggers.
Quick Look adds two helpful query parameters in the href:
<a rel="ar"
href="/models/chair.usdz#allowsContentScaling=0&canonicalWebPageURL=https://shop.example.com/chair">
...
</a>
allowsContentScaling=0— locks scale to authored size (a chair stays chair-sized).canonicalWebPageURL— the "Buy" button at the top of Quick Look links here. Conversion lever.checkoutTitle,checkoutSubtitle,price— show product info in the AR overlay.
4. Android — Scene Viewer (the intent URL)
Scene Viewer is launched by an Android intent:// URL with the glTF file as a parameter. The trick is the long, exact format. Get it slightly wrong and Chrome silently falls back to opening the glTF as a download.
function sceneViewerIntent(gltfUrl, title) {
const params = new URLSearchParams({
file: gltfUrl,
mode: 'ar_only', // or 'ar_preferred'
title: title || 'Product',
link: location.href, // back-link from the AR view
});
return 'intent://arvr.google.com/scene-viewer/1.0?' + params.toString() +
'#Intent;scheme=https;package=com.google.android.googlequicksearchbox;' +
'action=android.intent.action.VIEW;' +
'S.browser_fallback_url=' + encodeURIComponent(location.href) + ';end;';
}
Modes:
ar_only— straight into AR. Fails if device doesn't support it.ar_preferred— AR if available, otherwise the 3D viewer.3d_only— never AR, always the in-browser 3D viewer.
Use ar_preferred in production. Some devices report "supported" then fail at session start; ar_preferred degrades to 3D viewer, which is still useful.
5. WebXR — the in-browser path
WebXR runs in Chrome (Android, Quest browser) and Vision Pro Safari. It's the only route where you, the developer, render every frame. Three.js has first-class support via the WebXR manager.
renderer.xr.enabled = true;
const button = document.createElement('button');
button.textContent = 'Enter AR';
button.onclick = async () => {
const session = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['hit-test'],
optionalFeatures: ['dom-overlay'],
domOverlay: { root: document.body },
});
await renderer.xr.setSession(session);
};
Three.js ships an ARButton helper at three/addons/webxr/ARButton.js that wraps this with feature-detection and a styled button. In our demo we wire detection ourselves so you can see the moving parts.
WebXR's killer features:
- Hit testing against real-world surfaces — for "tap to place" UX.
- DOM overlay — your existing UI stays visible during the AR session.
- Anchors + persistence — the object stays put if the user walks away.
- Depth sensing (S11-13 covers this) — for occlusion.
6. Desktop — the QR fallback
Desktop browsers can't do AR. Don't pretend they can. Show a small QR code that encodes the current URL. The user scans with their phone, lands on the same page (with state), taps the AR button there. The pattern is universal — Apple, IKEA, Cartier all use it.
You don't need a QR library. Use a hosted QR generator (qrserver.com, QR Code Monkey API) or generate inline with a tiny library like qrcode-svg. For demos, an <img> with a public QR API URL is fine.
7. Live demo — one button, four routes
The button below detects your platform and shows the right CTA. On desktop you'll see the QR fallback. On iOS you'll see a Quick Look anchor (linked to a placeholder USDZ — replace with your real one). On Android, the Scene Viewer intent. WebXR if your browser supports it.
View in your space
Detecting your device…
8. The asset pipeline (glTF → USDZ + Scene Viewer-friendly glb)
One source of truth, two outputs:
# 1. Author in Blender / Maya / Rhino. Export glTF 2.0 with Draco compression.
# 2. Optimize for the web:
gltf-transform optimize chair.glb chair.web.glb \
--texture-compress webp --simplify 0.95
# 3. Generate USDZ for iOS:
gltf-transform usd chair.glb chair.usdz
# 4. Verify Scene Viewer compatibility:
# Scene Viewer prefers .glb (binary), KHR_materials_pbrSpecularGlossiness OFF.
# Animations supported, but only 1 active at a time.
Things that will silently break your AR experience if you skip them:
- Real-world scale. A chair must export at meter scale. Quick Look's
allowsContentScaling=0trusts your scale. - +Y up. glTF's convention. Don't override.
- USDZ texture format: PNG / JPEG, not WebP. Quick Look will reject WebP-textured USDZ.
- File size for cellular: target < 10 MB for the USDZ. Apple recommends < 25 MB but cellular shoppers will bounce above 10.
- Anchor / "ground plane": USDZ exports should be authored with the origin at the floor of the object, not the center of mass. AR Quick Look places the origin on the detected surface.
9. The <model-viewer> alternative
Google's <model-viewer> web component bundles all four routes into one tag. If your needs are simple — spin, click, AR — drop it in and ship.
<script type="module"
src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer
src="chair.glb"
ios-src="chair.usdz"
ar
ar-modes="webxr scene-viewer quick-look"
camera-controls
alt="A green chair">
</model-viewer>
When does it fall short and force a custom Three.js build?
- You need material-slot binding tied to a configurator UI (S11-01).
- You need an external state store (Zustand / Redux / your own) driving the viewer.
- You need a custom shader (clearcoat flake for car paint, anisotropy for watch dials).
- You need post-processing (bloom, SSAO, custom outlines).
- You need analytics on every interaction beyond
model-viewer's built-in events.
Most agency-tier configurators graduate from model-viewer within a year.
10. The conversion lift
Documented industry lifts when AR is implemented well:
| Metric | Lift | Source |
|---|---|---|
| Conversion rate | +20–40% | Threekit / Shopify case studies |
| Return rate | −25–40% | furniture / footwear retailers |
| Time on page | +70% | Cylindo (now Vertebrae) data |
| AR-engaged shoppers vs. baseline | 3–4× conversion | Apple developer docs (AR Quick Look) |
11. References
- Apple AR Quick Look gallery — every official example.
developer.apple.com/augmented-reality/quick-look/. - Google Scene Viewer docs — intent URL spec, supported features, debugging.
- WebXR Device API on MDN — session lifecycle, hit-test, anchors.
- Three.js
ARButton+ WebXR examples. - Google
<model-viewer>docs — modelviewer.dev. - gltf-transform usd — open-source USDZ pipeline.
12. Takeaways
- AR is four routes behind one button. iOS Quick Look, Android Scene Viewer, WebXR, QR fallback.
- iOS:
<a rel="ar">+ USDZ + canonicalWebPageURL. - Android:
intent://URL + glb +ar_preferred. - WebXR:
renderer.xr.enabled = true+requestSession('immersive-ar')+ dom-overlay. - Desktop: QR code to the same page. Don't fake AR.
- Asset pipeline matters more than the JS routing — meter scale, +Y up, < 10 MB USDZ.
- Drop
<model-viewer>in if your needs are simple. Graduate to Three.js when you need slots, shaders, or analytics.