JavaScript 事件(Events)和 DOM APIs
因为A-Frame本质上就是HTML,我们当然可以像普通Web开发一样使用JavaScript和 DOM API来控制其中的场景和实体。
Image by Ruben Mueller from The VR Jump.
场景中的每个元素,甚至 <a-box>
或者 <a-sky>
这样的元素,都是实体(通过 <a-entity>
来表示)。A-Frame修改了HTML元素原型来为特定的DOM API添加一些额外的行为。请查阅Entity API文档 来参考下面讨论到的大多数API详细用法。
A-Frame中如何使用JS
在讨论使用JavaScript和DOM API的不同方法之前, 我们推荐你把JS代码封装在 A-Frame组件中。组件可以使代码更好的模块化,使逻辑和行为在HTML页面层面可见,同时能确保代码在正确的时候被执行(例如,在场景和实体附加和初始化之后)。作为最简单的一个例子,注册一个 console.log
组件:
AFRAME.registerComponent('log', { schema: {type: 'string'}, init: function () { var stringToLog = this.data; console.log(stringToLog); }}); |
然后在HTML中使用该组件:
<a-scene log="Hello, Scene!"> <a-box log="Hello, Box!"></a-box></a-scene> |
组件封装代码为可重用、声明性和共享。但是,如果我们只是在运行时闲逛,我们可以使用我们的浏览器的开发者工具控制台在我们的场景中运行JavaScript。
通过查询和遍历获取实体
DOM作为场景图的强大之处在于,标准DOM提供了遍历、查询、查找和选择的工具: .querySelector()
和 .querySelectorAll()
。最早是借鉴自 jQuery 选择器,我们可以在 MDN上学习这部分基础知识。
让我们举几个查询选择器的实例,以下面的场景为例:
<html> <a-scene> <a-box color="red"></a-box> <a-sphere color="blue"></a-sphere> <a-box color="green"></a-box> <a-entity light="type: ambient"></a-entity> <a-entity light="type: directional"></a-entity> </a-scene></html> |
使用 .querySelector()
.querySelector()
用来查询单个元素。
var sceneEl = document.querySelector('a-scene'); |
注意,如果我们使用了组件,我们已经有一个场景元素的参考,不需要额外的查询。所有实体拥有一个场景元素的引用:
AFRAME.registerComponent('foo', { init: function () { console.log(this.el.sceneEl); // Reference to the scene element. }}); |
如果元素有ID,我们可以使用ID选择器(也就是 #<ID>
),元素ID通常在页面中是唯一的。通过查询选择器,我们可以将查询范围限制在任何范围内:
var sceneEl = document.querySelector('a-scene');console.log(sceneEl.querySelector('#redBox'));// <a-box color="red"></a-box> |
使用 .querySelectorAll()
.querySelectorAll()
用来查询多个元素。比如我们可以按标签名称来查询元素:
console.log(sceneEl.querySelectorAll('a-box'));// [// <a-box color="red"></a-box>,// <a-box color="green"></a-box>// ] |
我们可以按类名class来查询元素(也就是 .<CLASS_NAME>
)。比如获取所有带 clickable
class的元素:
console.log(sceneEl.querySelectorAll('.clickable'));// [// <a-box color="red"></a-box>// <a-sphere color="blue"></a-sphere>// ] |
我们可以查询包含某属性(某组件)(也就是 [<ATTRIBUTE_NAME>]
)的元素。比如获取所有包含light组件的实体:
console.log(sceneEl.querySelectorAll('[light]'));// [// <a-entity light="type: ambient"></a-entity>// <a-entity light="type: directional"></a-entity>// ] |
遍历调用.querySelectorAll()
返回的元素
对于 .querySelectorAll()
返回的多个元素, 我们可以使用 for
循环来遍历它。下面的代码使用通配符 *
查询所有元素并遍历之:
var els = sceneEl.querySelectorAll('*');for (var i = 0; i < els.length; i++) { console.log(els[i]);} |
修改A-Frame场景图
和处理普通HTML元素一样,我们可以使用JavaScript 和 DOM API来动态更新实体。
使用.createElement()
接口来创建一个实体
代码 document.createElement
将创建一个空的实体:
var el = document.createElement('a-entity'); |
但是,这个实体不会被初始化或成为场景的一部分,直到我们把它添加到场景中。
使用 .appendChild()
接口来添加一个实体到场景中
使用 .appendChild(element)
接口来添加实体到DOM中。对于添加元素到场景中,具体而言我们获得场景(scene)对象,创建实体,然后通过前述方法添加到场景中:
var sceneEl = document.querySelector('a-scene');var entityEl = document.createElement('a-entity');// Do `.setAttribute()`s to initialize the entity.sceneEl.appendChild(entityEl); |
注意 .appendChild()
在浏览器中是一个 异步(asynchronous) 操作。在DOM添加实体完成之前,我们不能对之进行实际的操作(比如调用 .getAttribute()
方法来获取属性)。如果我们需要查询一个刚刚添加的实体的属性,我们可以侦听该实体的 加载完成(loaded)
事件,或者把代码逻辑放在A-Frame组件里面,这样一旦实体准备好了就可以被执行:
var sceneEl = document.querySelector('a-scene');AFRAME.registerComponent('do-something-once-loaded', { init: function () { // This will be called after the entity has properly attached and loaded. console.log('I am ready'!); }});var entityEl = document.createElement('a-entity');entityEl.setAttribute('do-something-once-loaded', '');sceneEl.appendChild(entityEl); |
使用 .removeChild()
接口来移除一个实体
要从DOM也即从场景中删除实体,我们可以从父元素调用 .removeChild(element)
方法。如果我们有一个实体,我们需要访问其父元素 (parentNode
) 来删除它。
entityEl.parentNode.removeChild(entityEl); |
修改实体
空实体没有实际用处,我们可以通过添加组件、配置组件属性和删除组件来更新实体。
使用 .setAttribute()
方法来添加组件
我们可以使用 .setAttribute(componentName, data)
方法来添加一个组件。例子1:我们添加一个几何模型(geometry)组件到实体中。
entityEl.setAttribute('geometry', { primitive: 'box', height: 3, width: 1}); |
例子2,我们添加一个物理(physics)组件到实体中:
entityEl.setAttribute('dynamic-body', { shape: 'box', mass: 1.5, linearDamping: 0.005}); |
和普通HTML元素的.setAttribute()
方法不同,一个实体的 .setAttribute()
方法被改进为接受多种类型的参数如 objects, 还可以用来更新组件的某个属性。详情请阅读: Entity.setAttribute()
。
使用 .setAttribute()
方法来更新组件
我们可以使用 .setAttribute()
来更新一个组件。更新组件有若干种形式:
更新单属性组件的属性
比如更新 位置组件,这是一个单属性组件。我们可以传递一个对象或者一个字符串。推荐使用对象因为这样A-Frame无需解析字符串。
entityEl.setAttribute('position', {x: 1, y: 2: z: -3});// Or entityEl.setAttribute('position', '1 2 -3'); |
更新多属性组件的单个属性
比如更新 材料(material)组件, 这是一个多属性组件。我们传递组件名称,属性名称以及属性取值给到 .setAttribute()
方法:
entityEl.setAttribute('material', 'color', 'red'); |
更新多属性组件的多个属性
比如要一次性更新 光照(light)组件 的多个属性,我们提供组件名称以及一个属性对象给到 .setAttribute()
方法。我们更新光照的颜色和强度,但保持类型不变:
// <a-entity light="type: directional; color: #CAC; intensity: 0.5"></a-entity>entityEl.setAttribute('light', {color: '#ACC', intensity: 0.75});// <a-entity light="type: directional; color: #ACC; intensity: 0.75"></a-entity> |
替换多属性组件的全部属性
比如要替换掉 几何模型(geometry)组件 的全部属性,我们传递组件名称和一个属性对象给到 .setAttribute()
方法,以及一个标志(flag)来指定完全替换现有属性。代码如下:
// <a-entity geometry="primitive: cylinder; height: 4; radius: 2"></a-entity>entityEl.setAttribute('geometry', {primitive: 'torusKnot', p: 1, q: 3, radiusTubular: 4}, true);// <a-entity geometry="primitive: torusKnot; p: 1; q: 3; radiusTubular: 4"></a-entity> |
使用 .removeAttribute()
删除一个组件
要从实体中删除一个组件,我们可以使用 .removeAttribute(componentName)
。让我们从相机(camera)实体中删除默认的 wasd-controls
:
var cameraEl = document.querySelector('[camera]');cameraEl.removeAttribute('wasd-controls'); |
事件和事件侦听器
对于JavaScript和DOM,实体和组件有一个相互通信的简单方法:事件和事件侦听器。事件是一种信号发射方式,其他代码可以侦听该信号并响应。阅读更多:浏览器事件。
使用 .emit()
来发出一个事件
A-Frame 元素提供如下方法来发射自定义事件:.emit(eventName, eventDetail, bubbles)
。例如,假设我们在构建一组物理组件,并希望实体在发生碰撞时发出信号:
entityEl.emit('physicscollided', {collidingEntity: anotherEntityEl}, false); |
然后代码的其他部分可以等待并侦听这个事件并执行代码来响应。我们可以通过事件处理方法的第二个参数来传递事件细节信息和数据。我们还可以指定事件是否冒泡,也就是说其父实体是否也将发出事件。所以代码的其他部分就是注册一个事件侦听器。
使用 .addEventListener()
方法来添加事件侦听器
和普通HTML元素类似,我们可以使用 .addEventListener(eventName, function)
来注册一个事件侦听器。当eventName所对应的事件发生时,function函数将被调用并处理事件。例如,继续前一个物理系统的例子,当发生碰撞事件时:
entityEl.addEventListener('physicscollided', function (event) { console.log('Entity collided with', event.detail.collidingEntity);}); |
当实体发出 physicscollided
事件时,碰撞处理函数将被调用。在事件对象中,我们有事件详细信息,其中包含传递的事件数据和信息。
使用 .removeEventListener()
来删除一个事件侦听器
类似的,当我们想删除一个已有事件侦听器时,我们调用 .removeEventListener(eventName, function)
方法。我们必须传递和侦听器注册时相同的事件名称和函数。例如,继续前述物理碰撞事件:
// We have to define this function with a name if we later remove it.function collisionHandler (event) { console.log('Entity collided with', event.detail.collidingEntity);});entityEl.addEventListener('physicscollided', collisionHandler);entityEl.removeEventListener('physicscollided', collisionHandler); |