WebXR Anchors: Creating Persistent AR Experiences
XRWebXR
webxranchorsarspatialpersistence
WebXR Anchors: Creating Persistent AR Experiences
Anchors solve a fundamental AR problem: keeping virtual content attached to real-world locations. Without anchors, AR objects drift as you move.
The Anchor Problem
Traditional AR places objects relative to the camera. As you walk around:
- Objects don't stay "pinned" to real locations
- Returning to a spot doesn't show previous content
- Multi-user experiences are nearly impossible
Anchors solve all three.
Anchor API
Check Support
const session = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['anchors']
});
const anchorsSupported = session.enabledFeatures?.includes('anchors');
Create Anchor
async function createAnchor(position, orientation) {
const anchor = await session.addAnchor(
new XRRigidTransform(position, orientation)
);
return anchor;
}
Anchor from Hit Test
async function placeAnchorAtTap() {
const hitTestSource = await session.requestHitTestSource({ space: viewerSpace });
session.requestAnimationFrame((time, frame) => {
const results = frame.getHitTestResults(hitTestSource);
if (results.length > 0) {
const pose = results[0].getPose(referenceSpace);
const anchor = await frame.createAnchor(pose, referenceSpace);
placeObjectAtAnchor(anchor);
}
});
}
Working with Anchors
Store Anchor Pose
const anchors = new Map();
function trackAnchor(anchor, objectId) {
anchors.set(anchor, {
id: objectId,
createdAt: Date.now()
});
}
function updateAnchors(frame, referenceSpace) {
for (const [anchor, data] of anchors) {
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
if (pose) {
updateObjectPosition(data.id, pose.transform);
}
}
}
Remove Anchor
function removeAnchor(anchor) {
anchor.delete();
anchors.delete(anchor);
}
Persistence Across Sessions
Save Anchor Data
async function saveAnchors() {
const anchorData = [];
for (const [anchor, data] of anchors) {
// Get current pose
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
anchorData.push({
id: data.id,
position: pose.transform.position,
orientation: pose.transform.orientation,
timestamp: Date.now()
});
}
// Save to localStorage or cloud
localStorage.setItem('arAnchors', JSON.stringify(anchorData));
}
Restore Anchors
async function restoreAnchors() {
const savedData = JSON.parse(localStorage.getItem('arAnchors') || '[]');
for (const data of savedData) {
const transform = new XRRigidTransform(
data.position,
data.orientation
);
const anchor = await frame.createAnchor(transform, referenceSpace);
placeObjectAtAnchor(anchor, data.id);
}
}
World Tracking
Persistent Anchor IDs
Some devices support persistent anchor IDs:
async function checkPersistence() {
if ('requestPersistentAnchor' in session) {
// Persistent anchors supported
const anchor = await session.requestPersistentAnchor('room-center');
}
}
Cloud Anchor Services
For cross-device persistence:
// Using a cloud anchor service
async function shareAnchor(anchor) {
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
const response = await fetch('/api/anchors', {
method: 'POST',
body: JSON.stringify({
position: pose.transform.position,
orientation: pose.transform.orientation,
environmentData: await captureEnvironmentMap()
})
});
const { anchorId } = await response.json();
return anchorId;
}
Complete Example
class ARAnchorManager {
constructor(session) {
this.session = session;
this.anchors = new Map();
this.objects = new Map();
}
async placeObject(position, object) {
const transform = new XRRigidTransform(position);
const anchor = await this.session.addAnchor(transform);
this.anchors.set(anchor, object);
this.objects.set(object.id, anchor);
return anchor;
}
update(frame, referenceSpace) {
for (const [anchor, object] of this.anchors) {
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
if (pose) {
object.position.copy(pose.transform.position);
object.quaternion.copy(pose.transform.orientation);
}
}
}
removeObject(objectId) {
const anchor = this.objects.get(objectId);
if (anchor) {
anchor.delete();
this.anchors.delete(anchor);
this.objects.delete(objectId);
}
}
save() {
const data = [];
for (const [anchor, object] of this.anchors) {
data.push({
objectId: object.id,
// Position in world coordinates
});
}
localStorage.setItem('arAnchors', JSON.stringify(data));
}
}
Browser Support
| Feature | Chrome | Safari | Edge |
|---|---|---|---|
| Basic Anchors | 85+ | 16+ | 85+ |
| Persistent Anchors | Limited | 16+ | Limited |
| Hit Test | 79+ | 16+ | 79+ |
Best Practices
- Create anchors sparingly - They consume resources
- Clean up deleted anchors - Call
anchor.delete() - Handle tracking loss - Anchors may become invalid
- Test on real devices - Desktop AR doesn't exist
Conclusion
Anchors transform AR from a novelty into a practical technology. Content that persists in real locations enables navigation, gaming, and industrial applications.