aframe-webxr

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

A-Frame WebXR Skill

A-Frame WebXR 技能指南

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
  • 使用最少的JavaScript构建VR/AR体验
  • 创建跨平台WebXR应用(桌面端、移动端、头显设备)
  • 利用HTML原语快速制作3D场景原型
  • 实现VR控制器交互
  • 以声明式方式在网页中添加3D内容
  • 构建360°图片/视频体验
  • 开发包含命中测试的AR体验

Core Concepts

核心概念

1. Entity-Component-System (ECS)

1. 实体组件系统(ECS)

A-Frame uses an entity-component-system architecture where:
  • Entities are containers (like
    <div>
    in HTML)
  • Components add functionality/appearance to entities
  • Systems provide global functionality
html
<!-- Entity with components -->
<a-entity
  geometry="primitive: box; width: 2"
  material="color: red; metalness: 0.5"
  position="0 1.5 -3"
  rotation="0 45 0">
</a-entity>
Primitives are shortcuts for common entity + component combinations:
html
<!-- Primitive (shorthand) -->
<a-box color="red" position="0 1.5 -3" rotation="0 45 0" width="2"></a-box>

<!-- Equivalent entity-component form -->
<a-entity
  geometry="primitive: box; width: 2"
  material="color: red"
  position="0 1.5 -3"
  rotation="0 45 0">
</a-entity>
A-Frame采用实体组件系统架构,其中:
  • 实体是容器(类似HTML中的
    <div>
  • 组件为实体添加功能/外观
  • 系统提供全局功能
html
<!-- 带有组件的实体 -->
<a-entity
  geometry="primitive: box; width: 2"
  material="color: red; metalness: 0.5"
  position="0 1.5 -3"
  rotation="0 45 0">
</a-entity>
原语是实体+组件组合的快捷方式:
html
<!-- 原语(简写形式) -->
<a-box color="red" position="0 1.5 -3" rotation="0 45 0" width="2"></a-box>

<!-- 等效的实体-组件形式 -->
<a-entity
  geometry="primitive: box; width: 2"
  material="color: red"
  position="0 1.5 -3"
  rotation="0 45 0">
</a-entity>

2. Scene Setup

2. 场景搭建

Every A-Frame app starts with
<a-scene>
:
html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- Entities go here -->
      <a-box position="-1 0.5 -3" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
The scene automatically injects:
  • Default camera (position:
    0 1.6 0
    )
  • Look controls (mouse drag)
  • WASD controls (keyboard movement)
每个A-Frame应用都从
<a-scene>
开始:
html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- 实体放在这里 -->
      <a-box position="-1 0.5 -3" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
场景会自动注入以下内容:
  • 默认相机(位置:
    0 1.6 0
  • 视角控制(鼠标拖拽)
  • WASD控制(键盘移动)

3. Camera Systems

3. 相机系统

Default Camera (auto-injected if none specified):
html
<a-entity camera="active: true" look-controls wasd-controls position="0 1.6 0"></a-entity>
Custom Camera:
html
<a-camera position="0 2 5" look-controls wasd-controls="acceleration: 50"></a-camera>
Camera Rig (for independent movement and rotation):
html
<a-entity id="rig" position="0 0 0">
  <!-- Camera for head tracking -->
  <a-camera look-controls></a-camera>

  <!-- Movement applied to rig, not camera -->
</a-entity>
VR Camera Rig with Controllers:
html
<a-entity id="rig" position="0 0 0">
  <!-- Camera at eye level -->
  <a-camera position="0 1.6 0"></a-camera>

  <!-- Left hand controller -->
  <a-entity
    hand-controls="hand: left"
    laser-controls="hand: left">
  </a-entity>

  <!-- Right hand controller -->
  <a-entity
    hand-controls="hand: right"
    laser-controls="hand: right">
  </a-entity>
</a-entity>
默认相机(如果未指定则自动注入):
html
<a-entity camera="active: true" look-controls wasd-controls position="0 1.6 0"></a-entity>
自定义相机
html
<a-camera position="0 2 5" look-controls wasd-controls="acceleration: 50"></a-camera>
相机 rig(用于独立的移动和旋转):
html
<a-entity id="rig" position="0 0 0">
  <!-- 用于头部追踪的相机 -->
  <a-camera look-controls></a-camera>

  <!-- 移动应用于rig,而非相机 -->
</a-entity>
带控制器的VR相机 rig
html
<a-entity id="rig" position="0 0 0">
  <!-- 位于眼高的相机 -->
  <a-camera position="0 1.6 0"></a-camera>

  <!-- 左手控制器 -->
  <a-entity
    hand-controls="hand: left"
    laser-controls="hand: left">
  </a-entity>

  <!-- 右手控制器 -->
  <a-entity
    hand-controls="hand: right"
    laser-controls="hand: right">
  </a-entity>
</a-entity>

4. Lighting

4. 光照

Ambient Light (global illumination):
html
<a-entity light="type: ambient; color: #BBB; intensity: 0.5"></a-entity>
Directional Light (like sunlight):
html
<a-entity light="type: directional; color: #FFF; intensity: 0.8" position="1 2 1"></a-entity>
Point Light (radiates in all directions):
html
<a-entity light="type: point; color: #F00; intensity: 2; distance: 50" position="0 3 0"></a-entity>
Spot Light (cone-shaped beam):
html
<a-entity light="type: spot; angle: 45; intensity: 1.5" position="0 5 0" rotation="-90 0 0"></a-entity>
环境光(全局照明):
html
<a-entity light="type: ambient; color: #BBB; intensity: 0.5"></a-entity>
方向光(类似太阳光):
html
<a-entity light="type: directional; color: #FFF; intensity: 0.8" position="1 2 1"></a-entity>
点光源(向所有方向辐射):
html
<a-entity light="type: point; color: #F00; intensity: 2; distance: 50" position="0 3 0"></a-entity>
聚光灯(锥形光束):
html
<a-entity light="type: spot; angle: 45; intensity: 1.5" position="0 5 0" rotation="-90 0 0"></a-entity>

5. Materials and Textures

5. 材质与纹理

Standard Material:
html
<a-sphere
  material="color: #FF0000; metalness: 0.5; roughness: 0.3"
  position="0 1 -3">
</a-sphere>
Textured Material:
html
<a-assets>
  <img id="woodTexture" src="wood.jpg">
</a-assets>

<a-box material="src: #woodTexture" position="0 1 -3"></a-box>
Flat Shading (no lighting):
html
<a-plane material="shader: flat; color: #4CC3D9"></a-plane>
标准材质
html
<a-sphere
  material="color: #FF0000; metalness: 0.5; roughness: 0.3"
  position="0 1 -3">
</a-sphere>
纹理材质
html
<a-assets>
  <img id="woodTexture" src="wood.jpg">
</a-assets>

<a-box material="src: #woodTexture" position="0 1 -3"></a-box>
平面着色(无光照效果):
html
<a-plane material="shader: flat; color: #4CC3D9"></a-plane>

6. Animations

6. 动画

Property Animation:
html
<a-box
  position="0 1 -3"
  animation="property: rotation; to: 0 360 0; loop: true; dur: 5000">
</a-box>
Multiple Animations (use
animation__*
naming):
html
<a-sphere
  position="0 1 -3"
  animation__position="property: position; to: 0 3 -3; dir: alternate; loop: true; dur: 2000"
  animation__rotation="property: rotation; to: 360 360 0; loop: true; dur: 4000"
  animation__scale="property: scale; to: 1.5 1.5 1.5; dir: alternate; loop: true; dur: 1000">
</a-sphere>
Event-Based Animation:
html
<a-box
  color="blue"
  animation__mouseenter="property: scale; to: 1.2 1.2 1.2; startEvents: mouseenter"
  animation__mouseleave="property: scale; to: 1 1 1; startEvents: mouseleave"
  animation__click="property: rotation; from: 0 0 0; to: 0 360 0; startEvents: click">
</a-box>
属性动画
html
<a-box
  position="0 1 -3"
  animation="property: rotation; to: 0 360 0; loop: true; dur: 5000">
</a-box>
多动画(使用
animation__*
命名规则):
html
<a-sphere
  position="0 1 -3"
  animation__position="property: position; to: 0 3 -3; dir: alternate; loop: true; dur: 2000"
  animation__rotation="property: rotation; to: 360 360 0; loop: true; dur: 4000"
  animation__scale="property: scale; to: 1.5 1.5 1.5; dir: alternate; loop: true; dur: 1000">
</a-sphere>
基于事件的动画
html
<a-box
  color="blue"
  animation__mouseenter="property: scale; to: 1.2 1.2 1.2; startEvents: mouseenter"
  animation__mouseleave="property: scale; to: 1 1 1; startEvents: mouseleave"
  animation__click="property: rotation; from: 0 0 0; to: 0 360 0; startEvents: click">
</a-box>

7. Assets Management

7. 资源管理

Preload assets for better performance:
html
<a-scene>
  <a-assets>
    <!-- Images -->
    <img id="texture1" src="texture.jpg">
    <img id="skyTexture" src="sky.jpg">

    <!-- Videos -->
    <video id="video360" src="360video.mp4" autoplay loop></video>

    <!-- Audio -->
    <audio id="bgMusic" src="music.mp3" preload="auto"></audio>

    <!-- Models -->
    <a-asset-item id="tree" src="tree.gltf"></a-asset-item>

    <!-- Mixins (reusable component sets) -->
    <a-mixin id="redMaterial" material="color: red; metalness: 0.7"></a-mixin>
  </a-assets>

  <!-- Use assets -->
  <a-entity gltf-model="#tree" position="2 0 -5"></a-entity>
  <a-sphere mixin="redMaterial" position="0 1 -3"></a-sphere>
  <a-sky src="#skyTexture"></a-sky>
</a-scene>
预加载资源以提升性能:
html
<a-scene>
  <a-assets>
    <!-- 图片 -->
    <img id="texture1" src="texture.jpg">
    <img id="skyTexture" src="sky.jpg">

    <!-- 视频 -->
    <video id="video360" src="360video.mp4" autoplay loop></video>

    <!-- 音频 -->
    <audio id="bgMusic" src="music.mp3" preload="auto"></audio>

    <!-- 模型 -->
    <a-asset-item id="tree" src="tree.gltf"></a-asset-item>

    <!-- 混合(可复用的组件集合) -->
    <a-mixin id="redMaterial" material="color: red; metalness: 0.7"></a-mixin>
  </a-assets>

  <!-- 使用资源 -->
  <a-entity gltf-model="#tree" position="2 0 -5"></a-entity>
  <a-sphere mixin="redMaterial" position="0 1 -3"></a-sphere>
  <a-sky src="#skyTexture"></a-sky>
</a-scene>

8. Custom Components

8. 自定义组件

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
<a-box rotate-on-click="speed: 2" position="0 1 -3"></a-box>
注册自定义组件以封装逻辑:
javascript
AFRAME.registerComponent('rotate-on-click', {
  // 组件 schema(配置)
  schema: {
    speed: {type: 'number', default: 1}
  },

  // 生命周期:组件附加时调用一次
  init: function() {
    this.el.addEventListener('click', () => {
      this.rotating = !this.rotating;
    });
  },

  // 生命周期:每帧调用
  tick: function(time, timeDelta) {
    if (this.rotating) {
      var rotation = this.el.getAttribute('rotation');
      rotation.y += this.data.speed;
      this.el.setAttribute('rotation', rotation);
    }
  }
});
html
<a-box rotate-on-click="speed: 2" position="0 1 -3"></a-box>

Common Patterns

常见模式

Pattern 1: VR Controller Interactions

模式1:VR控制器交互

Problem: Enable object grabbing and manipulation in VR
Solution: Use hand-controls and custom grab component
html
<a-scene>
  <!-- VR Camera Rig -->
  <a-entity id="rig">
    <a-camera position="0 1.6 0"></a-camera>

    <a-entity
      id="leftHand"
      hand-controls="hand: left"
      laser-controls="hand: left">
    </a-entity>

    <a-entity
      id="rightHand"
      hand-controls="hand: right"
      laser-controls="hand: right">
    </a-entity>
  </a-entity>

  <!-- Grabbable objects -->
  <a-box class="grabbable" position="-1 1.5 -3" color="#4CC3D9"></a-box>
  <a-sphere class="grabbable" position="1 1.5 -3" color="#EF2D5E"></a-sphere>
</a-scene>

<script>
AFRAME.registerComponent('grabbable', {
  init: function() {
    var el = this.el;

    el.addEventListener('triggerdown', function(evt) {
      console.log('Grabbed by', evt.detail.hand);
      el.setAttribute('color', 'green');
    });

    el.addEventListener('triggerup', function(evt) {
      el.setAttribute('color', 'blue');
    });

    el.addEventListener('gripdown', function(evt) {
      // Attach object to controller
      var controllerEl = evt.detail.controller;
      controllerEl.object3D.attach(el.object3D);
    });

    el.addEventListener('gripup', function(evt) {
      // Detach from controller
      var sceneEl = el.sceneEl.object3D;
      sceneEl.attach(el.object3D);
    });
  }
});

// Apply grabbable component
document.querySelectorAll('.grabbable').forEach(el => {
  el.setAttribute('grabbable', '');
});
</script>
问题:在VR中实现物体抓取和操作
解决方案:使用hand-controls和自定义抓取组件
html
<a-scene>
  <!-- VR相机 Rig -->
  <a-entity id="rig">
    <a-camera position="0 1.6 0"></a-camera>

    <a-entity
      id="leftHand"
      hand-controls="hand: left"
      laser-controls="hand: left">
    </a-entity>

    <a-entity
      id="rightHand"
      hand-controls="hand: right"
      laser-controls="hand: right">
    </a-entity>
  </a-entity>

  <!-- 可抓取的物体 -->
  <a-box class="grabbable" position="-1 1.5 -3" color="#4CC3D9"></a-box>
  <a-sphere class="grabbable" position="1 1.5 -3" color="#EF2D5E"></a-sphere>
</a-scene>

<script>
AFRAME.registerComponent('grabbable', {
  init: function() {
    var el = this.el;

    el.addEventListener('triggerdown', function(evt) {
      console.log('被以下控制器抓取:', evt.detail.hand);
      el.setAttribute('color', 'green');
    });

    el.addEventListener('triggerup', function(evt) {
      el.setAttribute('color', 'blue');
    });

    el.addEventListener('gripdown', function(evt) {
      // 将物体附加到控制器
      var controllerEl = evt.detail.controller;
      controllerEl.object3D.attach(el.object3D);
    });

    el.addEventListener('gripup', function(evt) {
      // 从控制器分离
      var sceneEl = el.sceneEl.object3D;
      sceneEl.attach(el.object3D);
    });
  }
});

// 应用grabbable组件
document.querySelectorAll('.grabbable').forEach(el => {
  el.setAttribute('grabbable', '');
});
</script>

Pattern 2: 360° Image Gallery

模式2:360°图片画廊

Problem: Create an interactive 360° photo viewer
Solution: Use sky primitive and clickable thumbnails
html
<a-scene>
  <a-assets>
    <img id="city" src="city.jpg">
    <img id="forest" src="forest.jpg">
    <img id="beach" src="beach.jpg">
    <img id="city-thumb" src="city-thumb.jpg">
    <img id="forest-thumb" src="forest-thumb.jpg">
    <img id="beach-thumb" src="beach-thumb.jpg">
    <audio id="click-sound" src="click.mp3"></audio>
  </a-assets>

  <!-- 360 image sphere -->
  <a-sky id="image-360" src="#city" rotation="0 -130 0"></a-sky>

  <!-- Thumbnail menu -->
  <a-entity id="menu" position="0 1.6 -2">
    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #city-thumb"
      position="-1 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #city">
    </a-entity>

    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #forest-thumb"
      position="0 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #forest">
    </a-entity>

    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #beach-thumb"
      position="1 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #beach">
    </a-entity>
  </a-entity>

  <!-- Camera with cursor for gaze interaction -->
  <a-camera>
    <a-cursor raycaster="objects: .link"></a-cursor>
  </a-camera>
</a-scene>
问题:创建交互式360°图片查看器
解决方案:使用sky原语和可点击缩略图
html
<a-scene>
  <a-assets>
    <img id="city" src="city.jpg">
    <img id="forest" src="forest.jpg">
    <img id="beach" src="beach.jpg">
    <img id="city-thumb" src="city-thumb.jpg">
    <img id="forest-thumb" src="forest-thumb.jpg">
    <img id="beach-thumb" src="beach-thumb.jpg">
    <audio id="click-sound" src="click.mp3"></audio>
  </a-assets>

  <!-- 360°图片球体 -->
  <a-sky id="image-360" src="#city" rotation="0 -130 0"></a-sky>

  <!-- 缩略图菜单 -->
  <a-entity id="menu" position="0 1.6 -2">
    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #city-thumb"
      position="-1 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #city">
    </a-entity>

    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #forest-thumb"
      position="0 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #forest">
    </a-entity>

    <a-entity class="link"
      geometry="primitive: plane; width: 0.7; height: 0.7"
      material="shader: flat; src: #beach-thumb"
      position="1 0 0"
      sound="on: click; src: #click-sound"
      event-set__mouseenter="scale: 1.2 1.2 1"
      event-set__mouseleave="scale: 1 1 1"
      event-set__click="_target: #image-360; material.src: #beach">
    </a-entity>
  </a-entity>

  <!-- 带光标用于凝视交互的相机 -->
  <a-camera>
    <a-cursor raycaster="objects: .link"></a-cursor>
  </a-camera>
</a-scene>

Pattern 3: AR Hit Testing (Place Objects in Real World)

模式3:AR命中测试(在现实世界中放置物体)

Problem: Place virtual objects on detected real-world surfaces
Solution: Use ar-hit-test component
html
<a-scene
  webxr="optionalFeatures: hit-test, dom-overlay; overlayElement: #overlay"
  ar-hit-test="target: #furniture; type: footprint">

  <a-assets>
    <a-asset-item id="chair" src="chair.gltf"></a-asset-item>
  </a-assets>

  <!-- Object to place -->
  <a-entity id="furniture" gltf-model="#chair" scale="0.5 0.5 0.5"></a-entity>

  <!-- AR instructions overlay -->
  <div id="overlay" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
                           background: rgba(0,0,0,0.7); color: white; padding: 15px;
                           border-radius: 8px; font-family: sans-serif;">
    <p id="message">Tap to enter AR mode</p>
  </div>
</a-scene>

<script>
const sceneEl = document.querySelector('a-scene');
const message = document.getElementById('message');

sceneEl.addEventListener('enter-vr', function() {
  if (this.is('ar-mode')) {
    message.textContent = '';

    this.addEventListener('ar-hit-test-start', function() {
      message.innerHTML = 'Scanning environment, finding surface.';
    }, { once: true });

    this.addEventListener('ar-hit-test-achieved', function() {
      message.innerHTML = 'Tap on the screen to place the object.';
    }, { once: true });

    this.addEventListener('ar-hit-test-select', function() {
      message.textContent = 'Object placed!';
      setTimeout(() => message.textContent = '', 2000);
    }, { once: true });
  }
});

sceneEl.addEventListener('exit-vr', function() {
  message.textContent = 'Tap to enter AR mode';
});
</script>
问题:将虚拟物体放置在检测到的现实世界表面上
解决方案:使用ar-hit-test组件
html
<a-scene
  webxr="optionalFeatures: hit-test, dom-overlay; overlayElement: #overlay"
  ar-hit-test="target: #furniture; type: footprint">

  <a-assets>
    <a-asset-item id="chair" src="chair.gltf"></a-asset-item>
  </a-assets>

  <!-- 要放置的物体 -->
  <a-entity id="furniture" gltf-model="#chair" scale="0.5 0.5 0.5"></a-entity>

  <!-- AR说明覆盖层 -->
  <div id="overlay" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
                           background: rgba(0,0,0,0.7); color: white; padding: 15px;
                           border-radius: 8px; font-family: sans-serif;">
    <p id="message">点击进入AR模式</p>
  </div>
</a-scene>

<script>
const sceneEl = document.querySelector('a-scene');
const message = document.getElementById('message');

sceneEl.addEventListener('enter-vr', function() {
  if (this.is('ar-mode')) {
    message.textContent = '';

    this.addEventListener('ar-hit-test-start', function() {
      message.innerHTML = '正在扫描环境,寻找表面。';
    }, { once: true });

    this.addEventListener('ar-hit-test-achieved', function() {
      message.innerHTML = '点击屏幕放置物体。';
    }, { once: true });

    this.addEventListener('ar-hit-test-select', function() {
      message.textContent = '物体已放置!';
      setTimeout(() => message.textContent = '', 2000);
    }, { once: true });
  }
});

sceneEl.addEventListener('exit-vr', function() {
  message.textContent = '点击进入AR模式';
});
</script>

Pattern 4: Mouse/Gaze Interactions

模式4:鼠标/凝视交互

Problem: Enable click interactions with desktop mouse or VR gaze
Solution: Use cursor component and raycaster
html
<a-scene>
  <!-- Interactive objects -->
  <a-box
    class="interactive"
    position="-1 1.5 -3"
    color="#4CC3D9"
    event-set__mouseenter="color: yellow"
    event-set__mouseleave="color: #4CC3D9"
    event-set__click="scale: 1.5 1.5 1.5">
  </a-box>

  <a-sphere
    class="interactive"
    position="1 1.5 -3"
    color="#EF2D5E"
    event-set__click="color: orange; scale: 2 2 2">
  </a-sphere>

  <a-plane position="0 0 -4" rotation="-90 0 0" width="10" height="10" color="#7BC8A4"></a-plane>

  <!-- Camera with cursor -->
  <a-camera position="0 1.6 0">
    <!-- Raycaster targets .interactive class -->
    <a-cursor
      raycaster="objects: .interactive"
      fuse="true"
      fuse-timeout="1500">
    </a-cursor>
  </a-camera>
</a-scene>

<script>
// Advanced click handling with JavaScript
document.querySelectorAll('.interactive').forEach(el => {
  el.addEventListener('click', function(evt) {
    console.log('Clicked:', this.id || this.tagName);
    console.log('Intersection point:', evt.detail.intersection.point);
  });
});
</script>
问题:支持桌面鼠标或VR凝视的点击交互
解决方案:使用cursor组件和raycaster
html
<a-scene>
  <!-- 交互式物体 -->
  <a-box
    class="interactive"
    position="-1 1.5 -3"
    color="#4CC3D9"
    event-set__mouseenter="color: yellow"
    event-set__mouseleave="color: #4CC3D9"
    event-set__click="scale: 1.5 1.5 1.5">
  </a-box>

  <a-sphere
    class="interactive"
    position="1 1.5 -3"
    color="#EF2D5E"
    event-set__click="color: orange; scale: 2 2 2">
  </a-sphere>

  <a-plane position="0 0 -4" rotation="-90 0 0" width="10" height="10" color="#7BC8A4"></a-plane>

  <!-- 带光标的相机 -->
  <a-camera position="0 1.6 0">
    <!-- Raycaster 目标为.interactive类 -->
    <a-cursor
      raycaster="objects: .interactive"
      fuse="true"
      fuse-timeout="1500">
    </a-cursor>
  </a-camera>
</a-scene>

<script>
// 使用JavaScript进行高级点击处理
document.querySelectorAll('.interactive').forEach(el => {
  el.addEventListener('click', function(evt) {
    console.log('点击了:', this.id || this.tagName);
    console.log('交点:', evt.detail.intersection.point);
  });
});
</script>

Pattern 5: Dynamic Scene Generation

模式5:动态场景生成

Problem: Programmatically create and manipulate entities
Solution: Use JavaScript DOM manipulation
html
<a-scene>
  <a-camera position="0 1.6 5"></a-camera>
  <a-entity light="type: ambient; color: #888"></a-entity>
  <a-entity light="type: directional; color: #FFF" position="1 2 1"></a-entity>
</a-scene>

<script>
const scene = document.querySelector('a-scene');

// Create sphere
function createSphere(x, y, z, color) {
  const entity = document.createElement('a-entity');

  entity.setAttribute('geometry', {
    primitive: 'sphere',
    radius: 0.5
  });

  entity.setAttribute('material', {
    color: color,
    metalness: 0.5,
    roughness: 0.3
  });

  entity.setAttribute('position', {x, y, z});

  // Add animation
  entity.setAttribute('animation', {
    property: 'position',
    to: `${x} ${y + 1} ${z}`,
    dir: 'alternate',
    loop: true,
    dur: 2000
  });

  scene.appendChild(entity);
  return entity;
}

// Generate grid of spheres
for (let x = -3; x <= 3; x += 1.5) {
  for (let z = -5; z <= -2; z += 1.5) {
    const color = `#${Math.floor(Math.random()*16777215).toString(16)}`;
    createSphere(x, 1, z, color);
  }
}

// Listen to component changes
scene.addEventListener('componentchanged', function(evt) {
  console.log('Component changed:', evt.detail.name);
});

// Access Three.js objects directly
setTimeout(() => {
  const entities = document.querySelectorAll('a-entity[geometry]');
  entities.forEach(el => {
    el.object3D.visible = true; // Direct Three.js manipulation
  });
}, 1000);
</script>
问题:以编程方式创建和操作实体
解决方案:使用JavaScript DOM操作
html
<a-scene>
  <a-camera position="0 1.6 5"></a-camera>
  <a-entity light="type: ambient; color: #888"></a-entity>
  <a-entity light="type: directional; color: #FFF" position="1 2 1"></a-entity>
</a-scene>

<script>
const scene = document.querySelector('a-scene');

// 创建球体
function createSphere(x, y, z, color) {
  const entity = document.createElement('a-entity');

  entity.setAttribute('geometry', {
    primitive: 'sphere',
    radius: 0.5
  });

  entity.setAttribute('material', {
    color: color,
    metalness: 0.5,
    roughness: 0.3
  });

  entity.setAttribute('position', {x, y, z});

  // 添加动画
  entity.setAttribute('animation', {
    property: 'position',
    to: `${x} ${y + 1} ${z}`,
    dir: 'alternate',
    loop: true,
    dur: 2000
  });

  scene.appendChild(entity);
  return entity;
}

// 生成球体网格
for (let x = -3; x <= 3; x += 1.5) {
  for (let z = -5; z <= -2; z += 1.5) {
    const color = `#${Math.floor(Math.random()*16777215).toString(16)}`;
    createSphere(x, 1, z, color);
  }
}

// 监听组件变化
scene.addEventListener('componentchanged', function(evt) {
  console.log('组件已更改:', evt.detail.name);
});

// 直接访问Three.js对象
setTimeout(() => {
  const entities = document.querySelectorAll('a-entity[geometry]');
  entities.forEach(el => {
    el.object3D.visible = true; // 直接操作Three.js
  });
}, 1000);
</script>

Pattern 6: Environment and Skybox

模式6:环境与天空盒

Problem: Create immersive environments quickly
Solution: Use community components and 360 images
html
<html>
  <head>
    <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@fern-solutions/aframe-sky-background/dist/sky-background.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.5.0/dist/aframe-extras.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- Gradient sky -->
      <a-sky-background
        top-color="#4A90E2"
        bottom-color="#87CEEB">
      </a-sky-background>

      <!-- Or textured sky -->
      <!-- <a-sky src="sky.jpg" rotation="0 -130 0"></a-sky> -->

      <!-- Ocean -->
      <a-entity
        ocean="density: 20; width: 50; depth: 50; speed: 4"
        material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
        rotation="-90 0 0">
      </a-entity>

      <!-- Particle system for atmosphere -->
      <a-entity
        particle-system="preset: snow; particleCount: 2000; color: #FFF">
      </a-entity>

      <a-entity light="type: ambient; color: #888"></a-entity>
      <a-entity light="type: directional; color: #FFF; intensity: 0.7" position="1 2 1"></a-entity>
    </a-scene>
  </body>
</html>
问题:快速创建沉浸式环境
解决方案:使用社区组件和360°图片
html
<html>
  <head>
    <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@fern-solutions/aframe-sky-background/dist/sky-background.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.5.0/dist/aframe-extras.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- 渐变天空 -->
      <a-sky-background
        top-color="#4A90E2"
        bottom-color="#87CEEB">
      </a-sky-background>

      <!-- 或纹理天空 -->
      <!-- <a-sky src="sky.jpg" rotation="0 -130 0"></a-sky> -->

      <!-- 海洋 -->
      <a-entity
        ocean="density: 20; width: 50; depth: 50; speed: 4"
        material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
        rotation="-90 0 0">
      </a-entity>

      <!-- 用于氛围的粒子系统 -->
      <a-entity
        particle-system="preset: snow; particleCount: 2000; color: #FFF">
      </a-entity>

      <a-entity light="type: ambient; color: #888"></a-entity>
      <a-entity light="type: directional; color: #FFF; intensity: 0.7" position="1 2 1"></a-entity>
    </a-scene>
  </body>
</html>

Pattern 7: GLTF Model Loading

模式7:GLTF模型加载

Problem: Load and display 3D models
Solution: Use gltf-model component with asset management
html
<a-scene>
  <a-assets>
    <a-asset-item id="robot" src="robot.gltf"></a-asset-item>
    <a-asset-item id="building" src="building.glb"></a-asset-item>
  </a-assets>

  <!-- Load model -->
  <a-entity
    gltf-model="#robot"
    position="0 0 -3"
    scale="0.5 0.5 0.5"
    animation="property: rotation; to: 0 360 0; loop: true; dur: 10000">
  </a-entity>

  <!-- Load with extras (animations) -->
  <a-entity
    gltf-model="#building"
    position="5 0 -10"
    animation-mixer="clip: *; loop: repeat">
  </a-entity>

  <a-camera position="0 1.6 5"></a-camera>
  <a-entity light="type: ambient; intensity: 0.5"></a-entity>
  <a-entity light="type: directional; intensity: 0.8" position="2 4 2"></a-entity>
</a-scene>

<script>
// Handle model loading events
document.querySelector('[gltf-model="#robot"]').addEventListener('model-loaded', (evt) => {
  console.log('Model loaded:', evt.detail.model);

  // Access Three.js object
  const model = evt.detail.model;
  model.traverse(node => {
    if (node.isMesh) {
      console.log('Mesh found:', node.name);
    }
  });
});

document.querySelector('[gltf-model="#robot"]').addEventListener('model-error', (evt) => {
  console.error('Model loading error:', evt.detail);
});
</script>
问题:加载并显示3D模型
解决方案:结合资源管理使用gltf-model组件
html
<a-scene>
  <a-assets>
    <a-asset-item id="robot" src="robot.gltf"></a-asset-item>
    <a-asset-item id="building" src="building.glb"></a-asset-item>
  </a-assets>

  <!-- 加载模型 -->
  <a-entity
    gltf-model="#robot"
    position="0 0 -3"
    scale="0.5 0.5 0.5"
    animation="property: rotation; to: 0 360 0; loop: true; dur: 10000">
  </a-entity>

  <!-- 加载带额外内容(动画)的模型 -->
  <a-entity
    gltf-model="#building"
    position="5 0 -10"
    animation-mixer="clip: *; loop: repeat">
  </a-entity>

  <a-camera position="0 1.6 5"></a-camera>
  <a-entity light="type: ambient; intensity: 0.5"></a-entity>
  <a-entity light="type: directional; intensity: 0.8" position="2 4 2"></a-entity>
</a-scene>

<script>
// 处理模型加载事件
document.querySelector('[gltf-model="#robot"]').addEventListener('model-loaded', (evt) => {
  console.log('模型已加载:', evt.detail.model);

  // 访问Three.js对象
  const model = evt.detail.model;
  model.traverse(node => {
    if (node.isMesh) {
      console.log('找到网格:', node.name);
    }
  });
});

document.querySelector('[gltf-model="#robot"]').addEventListener('model-error', (evt) => {
  console.error('模型加载错误:', evt.detail);
});
</script>

Integration Patterns

集成模式

With Three.js

与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);
访问底层Three.js对象:
javascript
// 获取Three.js场景
const scene = document.querySelector('a-scene').object3D;

// 获取实体的Three.js对象
const box = document.querySelector('a-box');
const threeObject = box.object3D;

// 直接操作Three.js
threeObject.position.set(1, 2, 3);
threeObject.rotation.y = Math.PI / 4;

// 添加自定义Three.js对象
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)

与GSAP(动画)集成

Animate A-Frame entities with GSAP:
html
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
const box = document.querySelector('a-box');

// Animate position
gsap.to(box.object3D.position, {
  x: 3,
  y: 2,
  z: -5,
  duration: 2,
  ease: 'power2.inOut'
});

// Animate rotation
gsap.to(box.object3D.rotation, {
  y: Math.PI * 2,
  duration: 3,
  repeat: -1,
  ease: 'none'
});

// Animate attributes
gsap.to(box.components.material.material, {
  opacity: 0.5,
  duration: 1
});
</script>
使用GSAP动画A-Frame实体:
html
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
const box = document.querySelector('a-box');

// 动画位置
gsap.to(box.object3D.position, {
  x: 3,
  y: 2,
  z: -5,
  duration: 2,
  ease: 'power2.inOut'
});

// 动画旋转
gsap.to(box.object3D.rotation, {
  y: Math.PI * 2,
  duration: 3,
  repeat: -1,
  ease: 'none'
});

// 动画属性
gsap.to(box.components.material.material, {
  opacity: 0.5,
  duration: 1
});
</script>

With React

与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 (
    <a-scene ref={sceneRef}>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" />
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" />
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" />
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" />
      <a-sky color="#ECECEC" />
    </a-scene>
  );
}

export default VRScene;
在React组件中集成A-Frame:
jsx
import React, { useEffect, useRef } from 'react';
import 'aframe';

function VRScene() {
  const sceneRef = useRef(null);

  useEffect(() => {
    const scene = sceneRef.current;

    // 动态创建实体
    const entity = document.createElement('a-sphere');
    entity.setAttribute('position', '0 1.5 -3');
    entity.setAttribute('color', '#EF2D5E');
    scene.appendChild(entity);

    // 监听事件
    scene.addEventListener('enter-vr', () => {
      console.log('进入VR模式');
    });
  }, []);

  return (
    <a-scene ref={sceneRef}>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" />
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" />
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" />
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" />
      <a-sky color="#ECECEC" />
    </a-scene>
  );
}

export default VRScene;

Performance Best Practices

性能优化最佳实践

1. Use Asset Management

1. 使用资源管理

Preload assets to avoid blocking:
html
<a-assets>
  <img id="texture1" src="large-texture.jpg">
  <video id="video360" src="360video.mp4" preload="auto"></video>
  <a-asset-item id="model" src="complex-model.gltf"></a-asset-item>
</a-assets>
预加载资源以避免阻塞:
html
<a-assets>
  <img id="texture1" src="large-texture.jpg">
  <video id="video360" src="360video.mp4" preload="auto"></video>
  <a-asset-item id="model" src="complex-model.gltf"></a-asset-item>
</a-assets>

2. Pool Entities

2. 实体池化

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);
    }
  }
});
复用实体而非创建/销毁:
javascript
AFRAME.registerComponent('bullet-pool', {
  init: function() {
    this.pool = [];
    this.used = [];

    // 预创建子弹
    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

3. 优化几何体

Use low-poly models and LOD:
html
<!-- Low-poly for distant objects -->
<a-sphere radius="1" segments-width="8" segments-height="6"></a-sphere>

<!-- High-poly for close objects -->
<a-sphere radius="1" segments-width="32" segments-height="32"></a-sphere>
使用低多边形模型和LOD(细节层次):
html
<!-- 远处物体使用低多边形 -->
<a-sphere radius="1" segments-width="8" segments-height="6"></a-sphere>

<!-- 近处物体使用高多边形 -->
<a-sphere radius="1" segments-width="32" segments-height="32"></a-sphere>

4. Limit Draw Calls

4. 限制绘制调用

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);
  }
});
对重复物体使用实例化:
javascript
AFRAME.registerComponent('instanced-trees', {
  init: function() {
    // 对重复几何体使用Three.js InstancedMesh
    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);

    // 定位实例
    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

5. 节流tick()函数

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;
    }
  }
});
如果不需要,不要每帧都更新:
javascript
AFRAME.registerComponent('throttled-update', {
  init: function() {
    this.lastUpdate = 0;
    this.updateInterval = 100; // 毫秒
  },

  tick: function(time, timeDelta) {
    if (time - this.lastUpdate >= this.updateInterval) {
      // 在这里执行耗时操作
      this.lastUpdate = time;
    }
  }
});

6. Use Stats Component for Monitoring

6. 使用Stats组件监控

html
<a-scene stats>
  <!-- Shows FPS and performance metrics -->
</a-scene>
html
<a-scene stats>
  <!-- 显示FPS和性能指标 -->
</a-scene>

Common Pitfalls and Solutions

常见陷阱与解决方案

Pitfall 1: Entities Not Appearing

陷阱1:实体不显示

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 <a-scene>
问题:添加了实体但不可见
原因
  • 实体位于相机后方
  • 缩放为0或极小
  • 材质不透明度为0
  • 实体在相机视锥体之外
解决方案
javascript
// 等待场景加载完成
const scene = document.querySelector('a-scene');
scene.addEventListener('loaded', () => {
  const entity = document.createElement('a-box');
  entity.setAttribute('position', '0 1.5 -3'); // 在相机前方
  entity.setAttribute('color', 'red');
  scene.appendChild(entity);
});

// 调试:检查实体位置
console.log(entity.getAttribute('position'));

// 调试:检查实体是否在场景中
console.log(entity.parentNode); // 应该是<a-scene>

Pitfall 2: Events Not Firing

陷阱2:事件不触发

Problem: Click/mouseenter events don't trigger
Cause: Missing raycaster or cursor
Solution:
html
<!-- Add cursor to camera -->
<a-camera>
  <a-cursor raycaster="objects: .interactive"></a-cursor>
</a-camera>

<!-- Add class to interactive objects -->
<a-box class="interactive" position="0 1 -3"></a-box>

<!-- Or use raycaster directly -->
<a-entity raycaster="objects: [geometry]" cursor></a-entity>
问题:点击/鼠标悬停事件未触发
原因:缺少raycaster或cursor
解决方案
html
<!-- 给相机添加光标 -->
<a-camera>
  <a-cursor raycaster="objects: .interactive"></a-cursor>
</a-camera>

<!-- 给交互式对象添加类 -->
<a-box class="interactive" position="0 1 -3"></a-box>

<!-- 或直接使用raycaster -->
<a-entity raycaster="objects: [geometry]" cursor></a-entity>

Pitfall 3: Performance Degradation

陷阱3:性能下降

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;
    });
  }
});
问题:实体过多导致FPS降低
原因
  • 绘制调用过多
  • 几何体复杂
  • 纹理未优化
  • tick()更新过于频繁
解决方案
javascript
// 1. 使用对象池(参见性能部分)
// 2. 简化几何体
// 3. 优化纹理(缩小尺寸,使用压缩)
// 4. 节流更新

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);

      // 隐藏远处的实体
      el.object3D.visible = distance < 50;
    });
  }
});

Pitfall 4: Z-Fighting (Overlapping Surfaces)

陷阱4:Z轴冲突(重叠表面)

Problem: Flickering when surfaces overlap
Cause: Two surfaces at same position
Solution:
html
<!-- Offset surfaces slightly -->
<a-plane position="0 0.01 0" rotation="-90 0 0"></a-plane>
<a-plane position="0 0.02 0" rotation="-90 0 0"></a-plane>

<!-- Or use renderOrder -->
<a-entity
  geometry="primitive: plane"
  material="src: #texture1; transparent: true"
  class="has-render-order">
</a-entity>

<script>
document.querySelector('.has-render-order').object3D.renderOrder = 1;
</script>
问题:表面重叠时出现闪烁
原因:两个表面位置相同
解决方案
html
<!-- 轻微偏移表面 -->
<a-plane position="0 0.01 0" rotation="-90 0 0"></a-plane>
<a-plane position="0 0.02 0" rotation="-90 0 0"></a-plane>

<!-- 或使用renderOrder -->
<a-entity
  geometry="primitive: plane"
  material="src: #texture1; transparent: true"
  class="has-render-order">
</a-entity>

<script>
document.querySelector('.has-render-order').object3D.renderOrder = 1;
</script>

Pitfall 5: Mobile VR Performance

陷阱5:移动VR性能问题

Problem: Low performance on mobile VR
Solutions:
html
<!-- Reduce renderer max canvas size -->
<a-scene renderer="maxCanvasWidth: 1920; maxCanvasHeight: 1920">

<!-- Use low-poly models -->
<a-sphere radius="1" segments-width="8" segments-height="6"></a-sphere>

<!-- Limit lights (expensive on mobile) -->
<a-entity light="type: ambient; intensity: 0.6"></a-entity>
<a-entity light="type: directional; intensity: 0.4" position="1 2 1"></a-entity>

<!-- Disable antialiasing if needed -->
<a-scene renderer="antialias: false">
</a-scene>
问题:移动VR设备上性能较低
解决方案
html
<!-- 降低渲染器最大画布尺寸 -->
<a-scene renderer="maxCanvasWidth: 1920; maxCanvasHeight: 1920">

<!-- 使用低多边形模型 -->
<a-sphere radius="1" segments-width="8" segments-height="6"></a-sphere>

<!-- 限制灯光数量(移动设备上灯光开销大) -->
<a-entity light="type: ambient; intensity: 0.6"></a-entity>
<a-entity light="type: directional; intensity: 0.4" position="1 2 1"></a-entity>

<!-- 必要时禁用抗锯齿 -->
<a-scene renderer="antialias: false">
</a-scene>

Pitfall 6: Asset Loading Issues

陷阱6:资源加载问题

Problem: Assets not loading or CORS errors
Solutions:
html
<!-- Use crossorigin attribute -->
<a-assets>
  <img id="texture" src="https://example.com/texture.jpg" crossorigin="anonymous">
</a-assets>

<!-- Wait for assets to load -->
<script>
const assets = document.querySelector('a-assets');
assets.addEventListener('loaded', () => {
  console.log('All assets loaded');
  // Safe to use assets now
});

assets.addEventListener('timeout', () => {
  console.error('Asset loading timeout');
});
</script>

<!-- Handle loading errors -->
<script>
const img = document.querySelector('img#texture');
img.addEventListener('error', () => {
  console.error('Failed to load texture');
  // Use fallback
  img.src = 'fallback-texture.jpg';
});
</script>
问题:资源加载失败或出现CORS错误
解决方案
html
<!-- 使用crossorigin属性 -->
<a-assets>
  <img id="texture" src="https://example.com/texture.jpg" crossorigin="anonymous">
</a-assets>

<!-- 等待资源加载完成 -->
<script>
const assets = document.querySelector('a-assets');
assets.addEventListener('loaded', () => {
  console.log('所有资源已加载');
  // 现在可以安全使用资源
});

assets.addEventListener('timeout', () => {
  console.error('资源加载超时');
});
</script>

<!-- 处理加载错误 -->
<script>
const img = document.querySelector('img#texture');
img.addEventListener('error', () => {
  console.error('纹理加载失败');
  // 使用备用资源
  img.src = 'fallback-texture.jpg';
});
</script>

Resources

资源

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)
  • threejs-webgl:用于A-Frame声明式API之外的高级Three.js控制
  • babylonjs-engine:采用不同架构的替代3D引擎
  • gsap-scrolltrigger:使用GSAP动画A-Frame实体
  • react-three-fiber:Three.js的React实现(与A-Frame的HTML方法对比)