---
name: aframe-webxr
description: Declarative web framework for building browser-based 3D, VR, and AR experiences using HTML and entity-component architecture. Use this skill when creating WebXR applications, VR experiences, AR experiences, 360-degree media viewers, or immersive web content with minimal JavaScript. Triggers on tasks involving A-Frame, WebXR, VR development, AR development, entity-component-system, declarative 3D, or HTML-based 3D scenes. Built on Three.js with accessible HTML-first approach.
---
# A-Frame WebXR Skill
## When to Use This Skill
- Build VR/AR experiences with minimal JavaScript
- Create cross-platform WebXR applications (desktop, mobile, headset)
- Prototype 3D scenes quickly with HTML primitives
- Implement VR controller interactions
- Add 3D content to web pages declaratively
- Build 360° image/video experiences
- Develop AR experiences with hit testing
## Core Concepts
### 1. Entity-Component-System (ECS)
A-Frame uses an entity-component-system architecture where:
- **Entities** are containers (like `
` in HTML)
- **Components** add functionality/appearance to entities
- **Systems** provide global functionality
```html
```
**Primitives** are shortcuts for common entity + component combinations:
```html
```
### 2. Scene Setup
Every A-Frame app starts with ``:
```html
```
The scene automatically injects:
- Default camera (position: `0 1.6 0`)
- Look controls (mouse drag)
- WASD controls (keyboard movement)
### 3. Camera Systems
**Default Camera** (auto-injected if none specified):
```html
```
**Custom Camera**:
```html
```
**Camera Rig** (for independent movement and rotation):
```html
```
**VR Camera Rig with Controllers**:
```html
```
### 4. Lighting
**Ambient Light** (global illumination):
```html
```
**Directional Light** (like sunlight):
```html
```
**Point Light** (radiates in all directions):
```html
```
**Spot Light** (cone-shaped beam):
```html
```
### 5. Materials and Textures
**Standard Material**:
```html
```
**Textured Material**:
```html
```
**Flat Shading** (no lighting):
```html
```
### 6. Animations
**Property Animation**:
```html
```
**Multiple Animations** (use `animation__*` naming):
```html
```
**Event-Based Animation**:
```html
```
### 7. Assets Management
Preload assets for better performance:
```html
```
### 8. Custom Components
Register custom components to encapsulate logic:
```javascript
AFRAME.registerComponent('rotate-on-click', {
// Component schema (configuration)
schema: {
speed: {type: 'number', default: 1}
},
// Lifecycle: called once when component attached
init: function() {
this.el.addEventListener('click', () => {
this.rotating = !this.rotating;
});
},
// Lifecycle: called every frame
tick: function(time, timeDelta) {
if (this.rotating) {
var rotation = this.el.getAttribute('rotation');
rotation.y += this.data.speed;
this.el.setAttribute('rotation', rotation);
}
}
});
```
```html
```
## Common Patterns
### Pattern 1: VR Controller Interactions
**Problem**: Enable object grabbing and manipulation in VR
**Solution**: Use hand-controls and custom grab component
```html
```
### Pattern 2: 360° Image Gallery
**Problem**: Create an interactive 360° photo viewer
**Solution**: Use sky primitive and clickable thumbnails
```html
```
### Pattern 3: AR Hit Testing (Place Objects in Real World)
**Problem**: Place virtual objects on detected real-world surfaces
**Solution**: Use ar-hit-test component
```html
Tap to enter AR mode
```
### Pattern 4: Mouse/Gaze Interactions
**Problem**: Enable click interactions with desktop mouse or VR gaze
**Solution**: Use cursor component and raycaster
```html
```
### Pattern 5: Dynamic Scene Generation
**Problem**: Programmatically create and manipulate entities
**Solution**: Use JavaScript DOM manipulation
```html
```
### Pattern 6: Environment and Skybox
**Problem**: Create immersive environments quickly
**Solution**: Use community components and 360 images
```html
```
### Pattern 7: GLTF Model Loading
**Problem**: Load and display 3D models
**Solution**: Use gltf-model component with asset management
```html
```
## Integration Patterns
### With Three.js
Access underlying Three.js objects:
```javascript
// Get Three.js scene
const scene = document.querySelector('a-scene').object3D;
// Get entity's Three.js object
const box = document.querySelector('a-box');
const threeObject = box.object3D;
// Direct Three.js manipulation
threeObject.position.set(1, 2, 3);
threeObject.rotation.y = Math.PI / 4;
// Add custom Three.js objects
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
```
### With GSAP (Animation)
Animate A-Frame entities with GSAP:
```html
```
### With React
Integrate A-Frame in React components:
```jsx
import React, { useEffect, useRef } from 'react';
import 'aframe';
function VRScene() {
const sceneRef = useRef(null);
useEffect(() => {
const scene = sceneRef.current;
// Create entities dynamically
const entity = document.createElement('a-sphere');
entity.setAttribute('position', '0 1.5 -3');
entity.setAttribute('color', '#EF2D5E');
scene.appendChild(entity);
// Listen to events
scene.addEventListener('enter-vr', () => {
console.log('Entered VR mode');
});
}, []);
return (
);
}
export default VRScene;
```
## Performance Best Practices
### 1. Use Asset Management
Preload assets to avoid blocking:
```html
```
### 2. Pool Entities
Reuse entities instead of creating/destroying:
```javascript
AFRAME.registerComponent('bullet-pool', {
init: function() {
this.pool = [];
this.used = [];
// Pre-create bullets
for (let i = 0; i < 20; i++) {
const bullet = document.createElement('a-sphere');
bullet.setAttribute('radius', 0.1);
bullet.setAttribute('visible', false);
this.el.sceneEl.appendChild(bullet);
this.pool.push(bullet);
}
},
getBullet: function() {
if (this.pool.length > 0) {
const bullet = this.pool.pop();
bullet.setAttribute('visible', true);
this.used.push(bullet);
return bullet;
}
},
returnBullet: function(bullet) {
bullet.setAttribute('visible', false);
const index = this.used.indexOf(bullet);
if (index > -1) {
this.used.splice(index, 1);
this.pool.push(bullet);
}
}
});
```
### 3. Optimize Geometry
Use low-poly models and LOD:
```html
```
### 4. Limit Draw Calls
Use instancing for repeated objects:
```javascript
AFRAME.registerComponent('instanced-trees', {
init: function() {
// Use Three.js InstancedMesh for repeated geometry
const scene = this.el.sceneEl.object3D;
const geometry = new THREE.ConeGeometry(0.5, 2, 8);
const material = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const mesh = new THREE.InstancedMesh(geometry, material, 100);
// Position instances
for (let i = 0; i < 100; i++) {
const matrix = new THREE.Matrix4();
matrix.setPosition(
Math.random() * 20 - 10,
0,
Math.random() * 20 - 10
);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
}
});
```
### 5. Throttle tick() Functions
Don't update every frame if unnecessary:
```javascript
AFRAME.registerComponent('throttled-update', {
init: function() {
this.lastUpdate = 0;
this.updateInterval = 100; // ms
},
tick: function(time, timeDelta) {
if (time - this.lastUpdate >= this.updateInterval) {
// Expensive operation here
this.lastUpdate = time;
}
}
});
```
### 6. Use Stats Component for Monitoring
```html
```
## Common Pitfalls and Solutions
### Pitfall 1: Entities Not Appearing
**Problem**: Entity added but not visible
**Causes**:
- Entity positioned behind camera
- Scale is 0 or very small
- Material opacity is 0
- Entity outside camera frustum
**Solution**:
```javascript
// Wait for scene to load
const scene = document.querySelector('a-scene');
scene.addEventListener('loaded', () => {
const entity = document.createElement('a-box');
entity.setAttribute('position', '0 1.5 -3'); // In front of camera
entity.setAttribute('color', 'red');
scene.appendChild(entity);
});
// Debug: Check entity position
console.log(entity.getAttribute('position'));
// Debug: Check if entity is in scene
console.log(entity.parentNode); // Should be
```
### Pitfall 2: Events Not Firing
**Problem**: Click/mouseenter events don't trigger
**Cause**: Missing raycaster or cursor
**Solution**:
```html
```
### Pitfall 3: Performance Degradation
**Problem**: Low FPS with many entities
**Causes**:
- Too many draw calls
- Complex geometries
- Unoptimized textures
- Too many tick() updates
**Solutions**:
```javascript
// 1. Use object pooling (see Performance section)
// 2. Simplify geometry
// 3. Optimize textures (reduce size, use compression)
// 4. Throttle updates
AFRAME.registerComponent('optimize-far-entities', {
tick: function() {
const camera = this.el.sceneEl.camera;
const entities = document.querySelectorAll('[geometry]');
entities.forEach(el => {
const distance = el.object3D.position.distanceTo(camera.position);
// Hide distant entities
el.object3D.visible = distance < 50;
});
}
});
```
### Pitfall 4: Z-Fighting (Overlapping Surfaces)
**Problem**: Flickering when surfaces overlap
**Cause**: Two surfaces at same position
**Solution**:
```html
```
### Pitfall 5: Mobile VR Performance
**Problem**: Low performance on mobile VR
**Solutions**:
```html
```
### Pitfall 6: Asset Loading Issues
**Problem**: Assets not loading or CORS errors
**Solutions**:
```html
```
## Resources
- [A-Frame Documentation](https://aframe.io/docs/)
- [A-Frame GitHub](https://github.com/aframevr/aframe)
- [A-Frame School](https://aframe.io/school/)
- [A-Frame Community Components](https://github.com/c-frame)
- [WebXR Device API](https://www.w3.org/TR/webxr/)
- [Three.js Documentation](https://threejs.org/docs/) (A-Frame built on Three.js)
## Related Skills
- **threejs-webgl**: For advanced Three.js control beyond A-Frame's declarative API
- **babylonjs-engine**: Alternative 3D engine with different architecture
- **gsap-scrolltrigger**: For animating A-Frame entities with GSAP
- **react-three-fiber**: React approach to Three.js (compare with A-Frame's HTML approach)