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 可以拆成 三层:
-
声明式场景:用标签摆物体。类比:先写一张舞台道具清单,浏览器再把清单变成 3D 对象;这让新手不用一开始就写大量 three.js 初始化代码。
-
实体组件系统:
<a-entity>是空壳,geometry、material、position、light等属性是可插拔零件。类比:同一辆玩具车可以换轮子、换马达、换外壳,而不是每换一次都重造整车。 -
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 合成 click、mouseenter、mouseleave 等事件。
<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 压缩,运行时必须能找到解码器。
如果模型过大,可以先在资产流水线里压缩:
gltf-transform optimize robot.glb robot-web.glb --compress meshopt --texture-compress webp这个案例适合商品 3D 展示、虚拟展馆、角色预览。真正的重点不是“能加载”,而是加载后比例、动画、压缩和解码路径都可控。
- 把 A-Frame 当普通 HTML 布局:这些标签不会走 CSS 排版,位置、旋转、尺度要按 3D 坐标理解。
- 业务 JS 不写成组件:直接在全局脚本里改实体,容易踩初始化时机和复用问题;官方最佳实践建议把应用代码放进 components / systems。
- 模型不做性能体检:VR 对帧率敏感,面数、贴图、draw calls、灯光太多都会让头显体验变差。
- raycaster 扫全场景:交互对象不加 class 过滤时,每次检测都更贵,复杂展厅会明显掉帧。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 想快速做 Web 端 VR / AR / 3D 原型,并且团队熟悉 HTML / JavaScript。
- 教学、展览、营销页、艺术实验,需要让非图形工程师也能读懂场景结构。
- 已经使用 three.js / glTF 生态,但希望用声明式标签降低第一版成本。
- 交互以凝视、点击、基础控制器、轻量模型为主,真实感不是最高优先级。
不适用:
- AAA 级游戏、复杂物理、超大世界流式加载,这类更适合 Unity、Unreal 或专用引擎。
- 需要完全掌控渲染管线、shader、后处理和 GPU 资源生命周期的底层图形项目。
- 对移动端低配设备帧率要求极高,却又要塞大量动态光源、模型和粒子。
- 团队根本不愿接受 ECS 组件写法,只想把 2D DOM 操作方式原样搬进 3D。
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 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 上方更声明式、更面向体验原型的一层。
- 官方仓库:aframevr/aframe
- 官方文档:A-Frame Introduction
- 架构入门:Entity-Component-System
- 交互文档:Interactions & Controllers
- 模型加载:gltf-model component
- threejs —— 理解 A-Frame 底下的渲染基础。
- 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