跳转到内容

A-Frame — 用 HTML 搭 Web VR 场景

待复核

A-Frame 是一个用 HTML 写 3D、AR、VR 场景的 Web 框架。日常类比:它像把舞台搭建说明写成标签,<a-box> 是一个箱子,<a-sky> 是天空,<a-scene> 是整座舞台。

它不是从零替代浏览器图形能力,而是站在 three.js、WebGL / WebXR 上面,把相机、光照、模型、控制器这些细节包成更容易读的声明式结构。

你看到一段 A-Frame 页面时,先不要把它当“普通网页排版”。它更像一张 3D 场景清单:每个标签都代表场景里的一个实体,每个属性都在给实体挂能力。

官方 README 把它定位为 browser based 3D、AR、VR experiences 的框架;仓库长期在约 17k stars,价值点是让非图形专家也能快速做出可进入 WebXR 的原型。

不理解 A-Frame,下面这些事会很难解释:

  • 为什么一段看起来像 HTML 的代码,打开后却能变成立体空间,而不是普通 DOM 排版。
  • 为什么 A-Frame 强调 entity-component-system,而不是让你为每种物体都写继承类。
  • 为什么“能在桌面浏览器跑”和“在 VR 头显里舒服地跑”是两件事,后者对帧率和交互更苛刻。
  • 为什么同样加载 glTF 模型,Web 场景还要关心 CORS、模型比例、贴图尺寸、Draco / Meshopt 解码器。

A-Frame 可以拆成 三层

  1. 声明式场景:用标签摆物体。类比:先写一张舞台道具清单,浏览器再把清单变成 3D 对象;这让新手不用一开始就写大量 three.js 初始化代码。

  2. 实体组件系统<a-entity> 是空壳,geometrymaterialpositionlight 等属性是可插拔零件。类比:同一辆玩具车可以换轮子、换马达、换外壳,而不是每换一次都重造整车。

  3. WebXR 入口:A-Frame 替你处理进入沉浸式模式、默认相机、控制器、raycaster 等常见样板。类比:它不是景区本身,而是售票口、地图和基础安全绳,让你更快进场。

这三层合起来,A-Frame 的核心不是“HTML 也能画 3D”这么简单,而是把 Web 生态、three.js 能力和 VR 交互组织成一个可组合的写法。

案例 1:搭一个可进入 WebXR 的小展厅

Section titled “案例 1:搭一个可进入 WebXR 的小展厅”

官方 README 和 Introduction 都展示了最小场景:几个几何体、地面和天空就能构成一个可观察的 3D 空间。

<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" 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>

逐部分解释:

  • <a-scene>:整座 3D 舞台,A-Frame 会在里面创建 three.js scene、camera 和 renderer。
  • <a-box> / <a-sphere> / <a-cylinder>:内置 primitive,适合先表达空间关系。
  • position="-1 0.5 -3":三个数分别是 x、y、z,默认按米理解,z 为负表示在镜头前方。
  • <a-plane>:旋转成地面;<a-sky>:给整个空间一个背景,不负责真实光照。

这个案例适合产品展示、教学原型、活动页面的第一版:先证明“空间关系和视觉方向对”,再换复杂模型。

案例 2:给展品加凝视或鼠标点击反馈

Section titled “案例 2:给展品加凝视或鼠标点击反馈”

官方交互文档说明,WebGL 物体不会天然收到浏览器 click;A-Frame 用 cursor + raycaster 合成 clickmouseentermouseleave 等事件。

<script>
AFRAME.registerComponent('color-on-click', {
init: function () {
const colors = ['#EF2D5E', '#4CC3D9', '#FFC65D'];
let i = 0;
this.el.addEventListener('click', () => {
i = (i + 1) % colors.length;
this.el.setAttribute('material', 'color', colors[i]);
});
}
});
</script>
<a-scene>
<a-box class="clickable" position="0 1 -3" color="#EF2D5E" color-on-click></a-box>
<a-camera>
<a-entity cursor="fuse: true; fuseTimeout: 700"
raycaster="objects: .clickable"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: black; shader: flat"></a-entity>
</a-camera>
</a-scene>

逐部分解释:

  • color-on-click:自定义组件,把业务逻辑放进 A-Frame 生命周期里,而不是散落在页面末尾。
  • cursor="fuse: true":用户看着目标一小段时间后触发点击,适合没有手柄的设备。
  • raycaster="objects: .clickable":只检测可点对象,避免每帧和整座场景做碰撞测试。
  • addEventListener('click', ...):监听的是 A-Frame 合成事件,不是普通 DOM 直接点中 3D 网格。

这个案例适合 360 展厅、VR 教学问答、沉浸式菜单:交互先轻量,动作反馈要明确。

案例 3:加载 glTF 模型并保留动画入口

Section titled “案例 3:加载 glTF 模型并保留动画入口”

3D Models 和 gltf-model 文档都建议优先使用 glTF,因为它更像 Web 上的 3D 传输格式,能包含层级、材质、骨骼和动画。

<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@7.7.x/dist/aframe-extras.min.js"></script>
<a-scene gltf-model="meshoptDecoderPath: https://unpkg.com/meshoptimizer@0.19.0/meshopt_decoder.js;">
<a-assets>
<a-asset-item id="robot" src="/models/robot.glb"></a-asset-item>
</a-assets>
<a-entity gltf-model="#robot"
animation-mixer
position="0 0 -4"
scale="0.5 0.5 0.5"></a-entity>
</a-scene>

逐部分解释:

  • <a-assets>:让场景先知道要预加载哪些资源,减少模型还没到就开始渲染的混乱。
  • gltf-model="#robot":通过选择器引用资产,而不是把长 URL 写在每个实体上。
  • animation-mixer:来自 aframe-extras,用来播放模型自带动画。
  • scale="0.5 0.5 0.5":模型常有单位差异,先把比例调到人能看清的尺度。
  • meshoptDecoderPath:如果模型用了 Meshopt 压缩,运行时必须能找到解码器。

如果模型过大,可以先在资产流水线里压缩:

Terminal window
gltf-transform optimize robot.glb robot-web.glb --compress meshopt --texture-compress webp

这个案例适合商品 3D 展示、虚拟展馆、角色预览。真正的重点不是“能加载”,而是加载后比例、动画、压缩和解码路径都可控。

  1. 把 A-Frame 当普通 HTML 布局:这些标签不会走 CSS 排版,位置、旋转、尺度要按 3D 坐标理解。
  2. 业务 JS 不写成组件:直接在全局脚本里改实体,容易踩初始化时机和复用问题;官方最佳实践建议把应用代码放进 components / systems。
  3. 模型不做性能体检:VR 对帧率敏感,面数、贴图、draw calls、灯光太多都会让头显体验变差。
  4. raycaster 扫全场景:交互对象不加 class 过滤时,每次检测都更贵,复杂展厅会明显掉帧。

适用

  • 想快速做 Web 端 VR / AR / 3D 原型,并且团队熟悉 HTML / JavaScript。
  • 教学、展览、营销页、艺术实验,需要让非图形工程师也能读懂场景结构。
  • 已经使用 three.js / glTF 生态,但希望用声明式标签降低第一版成本。
  • 交互以凝视、点击、基础控制器、轻量模型为主,真实感不是最高优先级。

不适用

  • AAA 级游戏、复杂物理、超大世界流式加载,这类更适合 Unity、Unreal 或专用引擎。
  • 需要完全掌控渲染管线、shader、后处理和 GPU 资源生命周期的底层图形项目。
  • 对移动端低配设备帧率要求极高,却又要塞大量动态光源、模型和粒子。
  • 团队根本不愿接受 ECS 组件写法,只想把 2D DOM 操作方式原样搬进 3D。
  • 2015 年前后:Mozilla VR 团队把 WebVR 入门门槛往 HTML 方向拉低,A-Frame 开始出现。
  • 早期阶段:它把 three.js 的复杂样板包成 <a-scene> 和一组 primitives,让创作者先能“看见东西”。
  • 社区扩展期:A-Painter、A-Blast、A-Saturday-Night 等示例证明浏览器里也能做完整 VR 体验。
  • 独立维护后:项目从 Mozilla 系背景走向更独立的开源社区,Supermedium 相关维护者继续推进。
  • WebXR 时代:重点从“浏览器能不能进 VR”转向“不同头显、控制器、性能预算怎样稳定交付”。
  • A-Frame 的厉害之处是把 WebXR 样板压平,让人先用 HTML 建立 3D 直觉。
  • ECS 是它能扩展的关键:实体是容器,组件是能力,系统负责全局服务。
  • VR 开发不是只把画面做出来,还要持续盯住帧率、输入方式、用户舒适度和模型资产。
  • A-Frame 和 three.js 不是对立关系;A-Frame 是 three.js 上方更声明式、更面向体验原型的一层。
  • threejs —— A-Frame 是 three.js 上的声明式 WebXR 层,很多底层对象仍能访问。
  • gltf-transform —— glTF 模型进 A-Frame 前,常先用它做压缩和资产整理。
  • cannon-es —— 需要 3D 物理时,可和 A-Frame 场景组合出碰撞与重力体验。
  • playcanvas —— 同样面向 Web 3D,但更像完整在线引擎,和 A-Frame 的 HTML 写法形成对照。
  • phaser —— 2D 游戏入口,适合和 A-Frame 对比“平面互动”和“空间互动”的差异。
  • blender —— 负责建模和导出 glTF,A-Frame 负责在浏览器里加载和交互。
  • ar-js —— AR.js — 浏览器里跑 Web AR 标记追踪
  • freecad —— FreeCAD — 参数化 CAD
  • mind-ar-js —— MindAR — 不装原生 SDK 的浏览器图像/人脸 AR