In the entity-component-system pattern, a component is a reusable and modular chunk of data that we plug into an entity to add appearance, behavior, and/or functionality.
In A-Frame, components modify entities which are 3D objects in the scene. We mix and compose components together to build complex objects. They let us encapsulate three.js and JavaScript code into modules that we can use declaratively from HTML. Components are roughly analogous to CSS.
With the schema being the anatomy, the lifecycle methods are the physiology; the schema defines the shape of the data, the lifecycle handler methods use the data to modify the entity. The handlers will usually interact with the Entity API.
Method | Description |
---|---|
init | Called once when the component is initialized. Used to set up initial state and instantiate variables. |
update | Called both when the component is initialized and whenever any of the component's properties is updated (e.g, via setAttribute). Used to modify the entity. |
remove | Called when the component is removed from the entity (e.g., via removeAttribute) or when the entity is detached from the scene. Used to undo all previous modifications to the entity. |
tick | Called on each render loop or tick of the scene. Used for continuous changes or checks. |
play | Called whenever the scene or entity plays to add any background or dynamic behavior. Also called once when the component is initialized. Used to start or resume behavior. |
pause | Called whenever the scene or entity pauses to remove any background or dynamic behavior. Also called when the component is removed from the entity or when the entity is detached from the scene. Used to pause behavior. |
updateSchema | Called whenever any of the component's properties is updated. Can be used to dynamically modify the schema. |
Within the methods, we have access to the component prototype via this
:
Property | Description |
---|---|
this.data | Parsed component properties computed from the schema default values, mixins, and the entity's attributes. |
this.el | Reference to the [entity][entity] as an HTML element. |
this.el.sceneEl | Reference to the [scene][scene] as an HTML element. |
this.id | If the component can have [multiple instances][multiple], the ID of the individual instance of the component (e.g., foo from sound__foo ). |
.init ()
.init ()
is called once at the beginning of the component's lifecycle.
An entity can call the component's init
handler:
setAttribute
.appendChild
.The init
handler is often used to:
For example, a cursor component's init
would set state variables, bind
methods, and add event listeners:
AFRAME.registerComponent('cursor', {
// ...
init: function () {
// Set up initial state and variables.
this.intersection = null;
// Bind methods.
this.onIntersection = AFRAME.utils.bind(this.onIntersection, this);
// Attach event listener.
this.el.addEventListener('raycaster-intersection', this.onIntersection);
}
// ...
});
.update (oldData)
.update (oldData)
is called whenever the component's properties change,
including at the beginning of the component's lifecycle. An entity can call a
component's update
handler:
init ()
is called, at the beginning of component's lifecycle..setAttribute
.The update
handler is often used to:
this.data
.Granular modifications to the entity can be done by [diffing][diff] the current
dataset (this.data
) with the previous dataset before the update (oldData
).
A-Frame calls .update()
both at the beginning of a component's lifecycle and every
time a component's data changes (e.g., as a result of setAttribute
). The
update handler often uses this.data
to modify the entity. The update handler
has access to the previous state of a component's data via its first argument.
We can use the previous data of a component to tell exactly which
properties changed to do granular updates.
For example, the visible component's update
sets the visibility of
the entity.
AFRAME.registerComponent('visible', {
/**
* this.el is the entity element.
* this.el.object3D is the three.js object of the entity.
* this.data is the component's property or properties.
*/
update: function (oldData) {
this.el.object3D.visible = this.data;
}
// ...
});
.remove ()
.remove ()
is called whenever the component is detached from the entity. An
entity can call a component's remove
handler:
removeAttribute
.removeChild
).The remove
handler is often used to:
For example, when the [light component][light] is removed, the light component will remove the light object that it had previously set on the entity, thus removing it from the scene.
AFRAME.registerComponent('light', {
// ...
remove: function () {
this.el.removeObject3D('light');
}
// ...
});
.tick (time, timeDelta)
.tick ()
is called on each tick or frame of the scene's render loop. The scene
will call a component's tick
handler:
The tick
handler is often used to:
The tick
handler is provided the global uptime of the scene in milliseconds
(time
) and the time difference in milliseconds since the last frame
(timeDelta
). These can be used for interpolation or to only run parts of the
tick
handler on a set interval.
For example, the tracked controls component will progress the controller's animations, update the controller's position and rotation, and check for button presses.
AFRAME.registerComponent('tracked-controls', {
// ...
tick: function (time, timeDelta) {
this.updateMeshAnimation();
this.updatePose();
this.updateButtons();
}
// ...
});
.pause ()
.pause ()
is called when the entity or scene pauses. The entity can call a
component's pause
handler:
remove
handler is called.Entity.pause ()
.Scene.pause ()
(e.g., the Inspector is opened).The pause
handler is often used to:
For example, the sound component will pause the sound and remove an event listener that would have played a sound on an event:
AFRAME.registerComponent('sound', {
// ...
pause: function () {
this.pauseSound();
this.removeEventListener();
}
// ...
});
.play ()
.play ()
is called when the entity or scene resumes. The entity can call
a component's play
handler:
update
handler is called.Entity.play ()
.Scene.play ()
.The play
handler is often use to:
For example, the sound component will play the sound and update the event listener that would play a sound on an event:
AFRAME.registerComponent('sound', {
// ...
play: function () {
if (this.data.autoplay) { this.playSound(); }
this.updateEventListener();
}
// ...
});
.updateSchema (data)
.updateSchema ()
, if defined, is called on every update in order to check if
the schema needs to be dynamically modified.
The updateSchema
handler is often used to:
For example, the geometry component checks if the primitive
property changed to determine whether to update the schema for a different
type of geometry:
AFRAME.registerComponent('geometry', {
// ...
updateSchema: (newData) {
if (newData.primitive !== this.data.primitive) {
this.extendSchema(GEOMETRIES[newData.primitive].schema);
}
}
// ...
});
.flushToDOM ()
To save on CPU time on stringification, A-Frame will only update in debug mode the component’s serialized representation in the actual DOM. Calling flushToDOM () will manually serialize the component’s data and update the DOM:
document.querySelector('[geometry]').components.geometry.flushToDOM();