A-Frame is a three.js framework with an entity-component-system (ECS) architecture. ECS architecture is a common and desirable pattern in 3D and game development that follows the composition over inheritance and hierarchy principle.
The benefits of ECS include:

  1. Greater flexibility when defining objects by mixing and matching reusable parts.
  2. Eliminates the problems of long inheritance chains with complex interwoven functionality.
  3. Promotes clean design via decoupling, encapsulation, modularization, reusability.
  4. Most scalable way to build a VR application in terms of complexity.
  5. Proven architecture for 3D and VR development.
  6. Allows for extending new features (possibly sharing them as community components).


  1. 再利用可能なパーツを組み合わせてオブジェクトを定義する際の柔軟性が高い。
  2. 複雑な機能を織り交ぜた長い継承の問題を解消できる。
  3. デカップリング、カプセル化、モジュール化、再利用性によるクリーンな設計を促進する。
  4. VRアプリケーションを構築する上で、複雑さの点で最もスケーラブルな方法
  5. 3DおよびVR開発において実績のあるアーキテクチャ
  6. 新しい機能を拡張することができる(コミュニティ・コンポーネントとして共有することもできる)。

A basic definition of ECS involves:

  • Entities are container objects into which components can be attached. Entities are the base of all objects in the scene. Without components, entities neither do nor render anything, similar to empty <div>s.
  • Components are reusable modules or data containers that can be attached to entities to provide appearance, behavior, and/or functionality. Components are like plug-and-play for objects. All logic is implemented through components, and we define different types of objects by mixing, matching, and configuring components. Like alchemy!
  • Systems provide global scope, management, and services for classes of components. Systems are often optional, but we can use them to separate logic and data; systems handle the logic, components act as data containers.
  • エンティティは、コンポーネントを取り付けることができるコンテナオブジェクトです。エンティティは、シーン内のすべてのオブジェクトのベースとなります。コンポーネントがなければ、エンティティは何もできず、レンダリングもできず、空の<div>のようになります。
  • コンポーネントは、再利用可能なモジュールまたはデータコンテナで、外観、動作、機能を提供するためにエンティティに取り付けることができます。コンポーネントは、オブジェクトのプラグアンドプレイのようなものです。すべてのロジックはコンポーネントを介して実装され、コンポーネントを組み合わせ、マッチさせ、設定することで、さまざまなタイプのオブジェクトを定義します。錬金術のようなものです。
  • システムは、コンポーネントのクラスに対して、グローバルな範囲、管理、およびサービスを提供します。システムはオプションであることが多いですが、ロジックとデータを分離するために使用することができます。システムはロジックを処理し、コンポーネントはデータコンテナとして機能します。

ECS in A-Frame

A-Frame has APIs that represents each piece of ECS:

  • Entities are represented by the<a-entity> element and prototype.
  • Components are represented by HTML attributes on <a-entity>‘s. Underneath, components are objects containing a schema, lifecycle handlers, and methods. Components are registered via the AFRAME.registerComponent (name, definition) API.
  • Systems are represented by <a-scene>‘s HTML attributes. System are similar to components in definition. Systems are registered via the AFRAME.registerSystem (name, definition) API.

Syntax (構文)

We create <a-entity> and attach components as HTML attributes. Most components have multiple properties that are represented by a syntax similar to CSS. This syntax takes the form with a colon (:) separating property names from property values, and a semicolon (;) separating different property declarations:

<a-entity ${componentName}="${propertyName1}: ${propertyValue1}; ${propertyName2}: ${propertyValue2}">

For example, we have <a-entity> and attach the geometry, material, light, and position components with various properties and property values:

<a-entity geometry="primitive: sphere; radius: 1.5"
          light="type: point; color: white; intensity: 2"
          material="color: white; shader: flat; src: glow.jpg"
          position="0 0 -5"></a-entity>

Composition (構成)

From there, we could attach more components to add additional appearance, behavior, or functionality (e.g., physics). Or we could update the component values to configure the entity (either declaratively or through .setAttribute).

そこからさらにコンポーネントを追加して、外観、動作、機能(物理演算など)を追加することができます。また、コンポーネントの値を更新して、エンティティを設定することもできます(宣言的に、または .setAttribute を使用して)。

A common type of entity to compose from multiple components are the player’s hands in VR. The player’s hands can have many components: appearance, gestures, behaviors, interactivity with other objects.

We plug in components into a hand entity to provide it behavior as if we were attaching superpowers or augmentations for VR! Each of the components below have no knowledge of each other, but can be combined to define a complex entity:



Declarative DOM-Based ECS (宣言的なDOMベースのECS)

A-Frame takes ECS to another level by making it declarative and based on the DOM. Traditionally, ECS-based engines would create entities, attach components, update components, remove components all through code. But A-Frame has HTML and the DOM which makes ECS ergonomic and resolves many of its weaknesses. Below are abilities that the DOM provides for ECS:

A-Frameは、ECSをドキュメントオブジェクトモデル (DOM)をベースにした宣言型にすることで、ECSをさらに進化させます。従来、ECSベースのエンジンは、エンティティの作成、コンポーネントのアタッチ、コンポーネントの更新、コンポーネントの削除をすべてコードで行っていました。しかし、A-FrameにはHTMLとDOMがあり、ECSを人間工学的にし、その弱点の多くを解決します。以下は、DOMがECSに提供する機能です

  1. Referencing Other Entities with Query Selectors (クエリセレクタによる他のエンティティの参照): The DOM provides a powerful query selector system which lets us query the scene graph and select an entity or entities that match a condition. We can get references to entities by IDs, classes, or data attributes. Because A-Frame is based on HTML, we can use query selectors out of the box. document.querySelector('#player').
  2. Decoupled Cross-Entity Communication with Events(イベントを使った非結合のクロスエンティティコミュニケーション): The DOM provides the ability to listen to and emit events. This provides a publish-subscribe communication system between entities. Components don’t have to know about each other, they can just emit an event (which could bubble up), and other components can listen to those events without calling each other. ball.emit('collided').
  3. APIs for Lifecycle Management with DOM APIs(DOM APIによるライフサイクル管理のためのAPI): The DOM provides APIs to update HTML elements and the tree including .setAttribute, .removeAttribute, .createElement, and .removeChild. These can be used as is just like in normal web development.
  4. Entity-Filtering with Attribute Selectors(属性セレクタによるエンティティフィルタリング): The DOM provides attribute selectors which allows us to query for an entity or entities that have or don’t have certain HTML attributes. This means we can ask for entities that have or don’t have a certain set of components. document.querySelector('[enemy]:not([alive])').
  5. Declarativeness(宣言性): Lastly, the DOM provides HTML. A-Frame bridges between ECS and HTML making an already clean pattern declarative, readable, and copy-and-pasteable.

Extensibility (拡張性)

A-Frame components can do anything. Developers are given permissionless innovation to create components to extend any feature. Components have full access to JavaScript, three.js, and Web APIs (e.g., WebRTC, Speech Recognition).

We will later go over in detail how to write an A-Frame component. As a preview, the structure of a basic component may look like:

A-Frameのコンポーネントは何でもできます。開発者には、あらゆる機能を拡張するコンポーネントを作成するための許可なきイノベーションが与えられています。コンポーネントは、JavaScript、three.js、Web API(例:WebRTC、Speech Recognition)に完全にアクセスできます。


AFRAME.registerComponent('foo', {
  schema: {
    bar: {type: 'number'},
    baz: {type: 'string'}

  init: function () {
    // Do something when component first attached.

  update: function () {
    // Do something when component's data is updated.

  remove: function () {
    // Do something the component or its entity is detached.

  tick: function (time, timeDelta) {
    // Do something on every scene tick or frame.

Declarative ECS grants us the ability to write a JavaScript module and abstract it through HTML. Once the component is registered, we can declaratively plug this module of code into an entity via an HTML attribute. This code-to-HTML abstraction makes ECS powerful and easy to reason. foo is the name of the component we just registered, and the data contains bar and baz properties:

宣言型ECSは、JavaScriptのモジュールを書き、それをHTMLで抽象化することができます。コンポーネントが登録されると、このコードのモジュールをHTML属性を介してエンティティに宣言的にプラグインすることができます。foo は登録したばかりのコンポーネントの名前で、データには bar と baz のプロパティが含まれています。

Component-Based Development (コンポーネントベースの開発)

For building VR applications, we recommend placing all application code within components (and systems). An ideal A-Frame codebase consists purely of modular, encapsulated, and decoupled components. These components can be unit tested in isolation or alongside other components.

When an application is created solely with components, all parts of its codebase become reusable! Components can be shared for other developers to use or we can reuse them in our other projects. Or the components can be forked and modified to adapt to other use cases.

A simple ECS codebase might be structured like:




JavaScript, Events, DOM APIs

Since A-Frame is just HTML, we can control the scene and its entities using JavaScript and DOM APIs as we mostly would in ordinary web development.

Every element in the scene, even elements such as <a-box> or <a-sky>, are entities (represented as <a-entity>). A-Frame modifies the HTML element prototype to add some extra behavior for certain DOM APIs to tailor them to A-Frame. See the Entity API documentation for reference on most of the APIs discussed below.

A-Frameは単なるHTMLなので、通常のWeb開発と同じように、JavaScriptやDOM APIを使ってシーンやエンティティを制御することができます。

シーンを構成するすべての要素は、<a-box>や<a-sky>などの要素も含めて、エンティティ(<a-entity>として表現される)です。A-Frameでは、HTML要素のプロトタイプを変更して、特定のDOM APIに対して、A-Frameに合わせた動作を追加しています。以下で説明するほとんどのAPIのリファレンスについては、Entity APIのドキュメントを参照してください。

Where to Place JavaScript Code for A-Frame (どこにJSを書くか)

Important: Before we go over the different ways to use JavaScript and DOM APIs, we prescribe encapsulating your JavaScript code within A-Frame components. Components modularize code, make logic and behavior visible from HTML, and ensure that code is executed at the correct time (e.g., after the scene and entities have attached and initialized). As the most basic example, to register a console.log component before <a-scene>:
重要:JavaScriptとDOM APIのさまざまな使用方法を説明する前に、JavaScriptコードをA-Frameコンポーネント内にカプセル化することをお勧めします。コンポーネントは、コードをモジュール化し、ロジックとビヘイビアをHTMLから見えるようにし、コードが正しいタイミングで実行されるようにします(例:シーンとエンティティがアタッチされ、初期化された後)。最も基本的な例として、<a-scene>の前にconsole.logコンポーネントを登録することができます。

AFRAME.registerComponent('log', {
  schema: {type: 'string'},

  init: function () {
    var stringToLog =;
<a-scene log="Hello, Scene!">
  <a-box log="Hello, Box!"></a-box>

Components encapsulate all of our code to be reusable, declarative, and shareable. Though if we’re just poking around at runtime, we can use our browser’s Developer Tools Console to run JavaScript on our scene.

Do not try to put A-Frame-related JavaScript in a raw <script> tag after <a-scene> as we would with traditional 2D scripting. If we do, we’d have to take special measures to make sure code runs at the right time (see Running Content Scripts on the Scene).

コンポーネントは、すべてのコードをカプセル化して、再利用可能、宣言可能、共有可能にします。しかし、ランタイムには、ブラウザのDeveloper Tools Consoleを使って、シーン上でJavaScriptを実行することができます。

従来の2Dスクリプトのように、 A-Frame関連のJavaScriptを<a-scene>の後に生の<script>タグで記述するのはやめましょう。 そうすると、コードが適切なタイミングで実行されるように特別な措置を取らなければなりません(「コンテンツスクリプトをシーン上で実行する」を参照)。

Getting Entities by Querying and Traversing (クエリとトラバースによるエンティティの取得)

The wonderful thing about the DOM as a scene graph is that the standard DOM provides utilities for traversal, querying, finding, and selecting through .querySelector() and .querySelectorAll(). Originally inspired by jQuery selectors, we can learn about query selectors on MDN.

Let’s run a few example query selectors. Take the scene below for example.

シーングラフとしてのDOMの素晴らしい点は、標準的なDOMが.querySelector()と.querySelectorAll()によって、トラバース、クエリー、検索、選択のためのユーティリティを提供していることです。元々は jQuery のセレクタに触発されたものですが、MDN ではクエリセレクタについて学ぶことができます。


    <a-box id="redBox" class="clickable" color="red"></a-box>
    <a-sphere class="clickable" color="blue"></a-sphere>
    <a-box color="green"></a-box>
    <a-entity light="type: ambient"></a-entity>
    <a-entity light="type: directional"></a-entity>

With .querySelector()

If we want to grab just one element, we use .querySelector() which returns one element. Let’s grab the scene element:

var sceneEl = document.querySelector('a-scene');

Note if we were working within a component, we’d already have a reference to the scene element without needing to query. All entities have reference to their scene element:

AFRAME.registerComponent('foo', {
  init: function () {
    console.log(this.el.sceneEl);  // Reference to the scene element.

If an element has an ID, we can use an ID selector (i.e., #<ID>). Let’s grab the red box which has an ID. Before we did a query selector on the entire document. Here, we’ll do a query selector just within the scope of the scene. With query selectors, we’re able to limit the scope of the query to within any element:

var sceneEl = document.querySelector('a-scene');
// <a-box id="redBox" class="clickable" color="red"></a-box>

With .querySelectorAll()

If we want to grab a group of elements, we use .querySelectorAll() which returns an array of elements. We can query across element names:

// [
//  <a-box id="redBox" class="clickable" color="red"></a-box>,
//  <a-box color="green"></a-box>
// ]

We can query for elements that have a class with a class selector (i.e., .<CLASS_NAME>). Let’s grab every entity that has the clickable class:

// [
//  <a-box id="redBox" class="clickable" color="red"></a-box>
//  <a-sphere class="clickable" color="blue"></a-sphere>
// ]

We can query for elements containing an attribute (or in this case, a component) with an attribute selector (i.e., [<ATTRIBUTE_NAME>]). Let’s grab every entity that has a light:

// [
//  <a-entity light="type: ambient"></a-entity>
// <a-entity light="type: directional"></a-entity>
// ]

Retrieving Component Data with .getAttribute() .getAttribute()によるコンポーネントデータの取得

We can get the data of components of an entity via .getAttribute. A-Frame augments .getAttribute to return values rather than strings (e.g., returning objects in most cases since components usually consist of multiple properties, or returning an actual boolean for like .getAttribute('visible'). Often, .getAttribute will return the internal data object of the component so do not modify the object directly:

// <a-entity geometry="primitive: sphere; radius: 2"></a-entity>
// >> {"primitive": "sphere", "radius": 2, ...}

Modifying the A-Frame Scene Graph A-Frame Scene Graphの修正

With JavaScript and DOM APIs, we can dynamically add and remove entities as we would with normal HTML elements.

Creating an Entity with .createElement()

To create an entity, we can use document.createElement. This will give us a blank entity:

var el = document.createElement('a-entity');

Adding an Entity with .appendChild()

To add an entity to the DOM, we can use .appendChild(element). Specifically, we want to add it to our scene. We grab the scene, create the entity, and append the entity to our scene.

var sceneEl = document.querySelector('a-scene');
var entityEl = document.createElement('a-entity');
// Do `.setAttribute()`s to initialize the entity.

Note that .appendChild() is an asynchronous operation in the browser. Until the entity has finished appending to the DOM, we can’t do many operations on the entity (such as calling .getAttribute()). If we need to query an attribute on an entity that has just been appended, we can listen to the loaded event on the entity, or place logic in an A-Frame component so that it is executed once it is ready:

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

Modifying an Entity

A blank entity doesn’t do anything. We can modify the entity by adding components, configuring component properties, and removing components.

Adding a Component with .setAttribute()

To add a component, we can use .setAttribute(componentName, data). Let’s add a geometry component to the entity.

entityEl.setAttribute('geometry', {
  primitive: 'box',
  height: 3,
  width: 1

Unlike a normal HTML .setAttribute(), an entity’s .setAttribute() is improved to take a variety of types of arguments such as objects, or to be able to update a single property of a component. Read more about Entity.setAttribute().

Updating a Component with .setAttribute()

To update a component, we also use .setAttribute(). Updating a component takes several forms.

Updating Property of Single-Property Component

Let’s update the property of the position component, a single-property component. We can pass either an object or a string. It is slightly preferred to pass an object so A-Frame doesn’t have to parse the string.

entityEl.setAttribute('position', {x: 1, y: 2, z: -3});
// Read on to see why `entityEl.object3D.position.set(1, 2, -3)` is preferred though.

Updating Single Property of Multi-Property Component

Let’s update a single property of the material component, a multi-property component. We do this by providing the component name, property name, and then property value to .setAttribute():

entityEl.setAttribute('material', 'color', 'red');

Updating Multiple Properties of a Multi-Property Component

// <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>

Events and Event Listeners

With JavaScript and the DOM, there is an easy way for entities and components to communicate with one another: events and event listeners. Events are a way to send out a signal that other code can pick up and respond to. Read more about browser events.


Emitting an Event with .emit()

A-Frame elements provide an easy way to emit custom events with .emit(eventName, eventDetail, bubbles). For example, let’s say we are building a physics component and we want the entity to send out a signal when it has collided with another entity:
A-Frame要素では、.emit(eventName, eventDetail, bubbles)を使用して、カスタムイベントを簡単に発行できます。例えば、物理コンポーネントを作成していて、エンティティが他のエンティティと衝突したときに信号を送信するようにしたいとします。

entityEl.emit('physicscollided', {collidingEntity: anotherEntityEl}, false);

Then other parts of the code can wait and listen on this event and run code in response. We can pass information and data through the event detail as the second argument. And we can specify whether the event bubbles, meaning that the parent entities will also emit the event. So other parts of the code can register an event listener.

Adding an Event Listener with .addEventListener()

Like with normal HTML elements, we can register an event listener with .addEventListener(eventName, function). When the event the listener is registered to is emitted, then the function will be called and handle the event. For example, continuing from the previous example with the physics collision event:
通常のHTML要素と同様に、.addEventListener(eventName, function)でイベントリスナーを登録することができます。リスナーが登録されているイベントが発せられると、その関数が呼び出されてイベントを処理します。例えば、先ほどの例の続きで、物理的な衝突イベントを考えてみましょう。

entityEl.addEventListener('physicscollided', function (event) {
  console.log('Entity collided with', event.detail.collidingEntity);

When the entity emits the physicscollided event, the function will be called with the event object. Notably in the event object, we have the event detail which contains data and information passed through the event.

Creating the Box Mesh

Let’s create our three.js box mesh from the .init(), and we’ll later let the .update() handler handle all the property updates. To create a box in three.js, we’ll create a THREE.BoxBufferGeometry, THREE.MeshStandardMaterial, and finally a THREE.Mesh. Then we set the mesh on our entity to add the mesh to the three.js scene graph using .setObject3D(name, object):

.init()からthree.jsのボックスメッシュを作成し、後で.update()ハンドラでプロパティの更新をすべて処理するようにしましょう。three.jsでボックスを作成するには、THREE.BoxBufferGeometry、THREE.MeshStandardMaterial、そして最後にTHREE.Meshを作成します。そして、.setObject3D(name, object)を使って、three.jsのシーングラフにメッシュを追加するために、エンティティにメッシュを設定します。

AFRAME.registerComponent('box', {
  schema: {
    width: {type: 'number', default: 1},
    height: {type: 'number', default: 1},
    depth: {type: 'number', default: 1},
    color: {type: 'color', default: '#AAA'}

   * Initial creation and setting of the mesh.
  init: function () {
    var data =;
    var el = this.el;

    // Create geometry.
    this.geometry = new THREE.BoxBufferGeometry(data.width, data.height, data.depth);

    // Create material.
    this.material = new THREE.MeshStandardMaterial({color: data.color});

    // Create mesh.
    this.mesh = new THREE.Mesh(this.geometry, this.material);

    // Set mesh on entity.
    el.setObject3D('mesh', this.mesh);

Now let’s handle updates. If the geometry-related properties (i.e., width, height, depth) update, we’ll just recreate the geometry. If the material-related properties (i.e., color) update, we’ll just update the material in place. To access the mesh to update it, we use .getObject3D('mesh').

AFRAME.registerComponent('box', {
  schema: {
    width: {type: 'number', default: 1},
    height: {type: 'number', default: 1},
    depth: {type: 'number', default: 1},
    color: {type: 'color', default: '#AAA'}

  init: function () {
    var data =;
    var el = this.el;
    this.geometry = new THREE.BoxBufferGeometry(data.width, data.height, data.depth);
    this.material = new THREE.MeshStandardMaterial({color: data.color});
    this.mesh = new THREE.Mesh(this.geometry, this.material);
    el.setObject3D('mesh', this.mesh);

   * Update the mesh in response to property updates.
  update: function (oldData) {
    var data =;
    var el = this.el;

    // If `oldData` is empty, then this means we're in the initialization process.
    // No need to update.
    if (Object.keys(oldData).length === 0) { return; }

    // Geometry-related properties changed. Update the geometry.
    if (data.width !== oldData.width ||
        data.height !== oldData.height ||
        data.depth !== oldData.depth) {
      el.getObject3D('mesh').geometry = new THREE.BoxBufferGeometry(data.width, data.height,

    // Material-related properties changed. Update the material.
    if (data.color !== oldData.color) {
      el.getObject3D('mesh').material.color = new THREE.Color(data.color);