Technical Documentation: aframevr/aframe
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
A-Frame
Examples
Find more examples on the homepage, A Week of A-Frame, and WebVR Directory.
Features
:eyeglasses: Virtual Reality Made Simple: A-Frame handles the 3D and WebXR
boilerplate required to get running across platforms including mobile, desktop, and all headsets (compatible with a WebXR capable browser) just by dropping in <a-scene>.
:heart: Declarative HTML: HTML is easy to read and copy-and-paste. Since
A-Frame can be used from HTML, A-Frame is accessible to everyone: web
developers, VR and AR enthusiasts, educators, artists, makers, kids.
:electric_plug: Entity-Component Architecture: A-Frame is a powerful
framework on top of three.js, providing a declarative, composable, reusable
entity-component structure for three.js. While A-Frame can be used from HTML,
developers have unlimited access to JavaScript, DOM APIs, three.js, WebXR, and
WebGL.
:zap: Performance: A-Frame is a thin framework on top of three.js.
Although A-Frame uses the DOM, A-Frame does not touch the browser layout
engine. Performance is a top priority, being battle-tested on highly
interactive WebXR experiences.
:globe_with_meridians: Cross-Platform: Build VR and AR applications for any headset compatible with a WebXR capable browser. Don't have a headset or controllers? No
problem! A-Frame still works on standard desktop and smartphones.
:mag: Visual Inspector: A-Frame provides a built-in visual 3D inspector
with a workflow similar to a browser's developer tools and interface similar to
Unity. Open up any A-Frame scene and hit <ctrl> + <alt> + i.
:runner: Features: Hit the ground running with A-Frame's built-in
components such as geometries, materials, lights, animations, models,
raycasters, shadows, positional audio, tracked controllers. Get even further
with community components such as particle systems, physics, multiuser, oceans,
mountains, speech recognition, or teleportation!
Usage
Example
Build VR and AR scenes in the browser with just a few lines of HTML! To start playing
and publishing now, remix the starter example on:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>
<body>
<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>
</body>
</html>With A-Frame's entity-component
architecture, we can drop in community
components from the ecosystem (e.g., ocean, physics) and plug them into our
objects straight from HTML:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-particle-system-component.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-extras.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@fern-solutions/aframe-sky-background/dist/sky-background.umd.min.js"></script>
</head>
<body>
<a-scene>
<a-entity id="rain" particle-system="preset: rain; color: #24CAFF; particleCount: 5000"></a-entity>
<a-entity id="sphere" geometry="primitive: sphere"
material="color: #EFEFEF; shader: flat"
position="0 0.15 -5"
light="type: point; intensity: 15.7"
animation="property: position; easing: easeInOutQuad; dir: alternate; dur: 1000; to: 0 -0.10 -5; loop: true"></a-entity>
<a-entity id="ocean" ocean="density: 20; width: 50; depth: 50; speed: 4"
material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
rotation="-90 0 0"></a-entity>
<a-sky-background top-color="#EBEBF5" bottom-color="#B9B9D2"></a-sky-background>
<a-entity id="light" light="type: ambient; color: #888"></a-entity>
</a-scene>
</body>
</html>Builds
To use the latest stable build of A-Frame, include aframe.min.js:
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>To check out the stable and master builds, see the dist/ folder.
npm
npm install --save aframe
# Or yarn add aframeimport AFRAME from 'aframe'; // e.g., with Webpack or Vite.Local Development
git clone https://github.com/aframevr/aframe.git # Clone the repository.
cd aframe && npm install # Install dependencies.
npm start # Start the local development server.And open in your browser http://localhost:8080.
If you want to test the examples in VR, you need to run it with https:
npm run start:httpsLook at the "On Your Network (IPv4)" line in the console, and copy and paste
the url to https://hmd.link service.
Then open the browser in your headset and type hmd.link in the address bar
then click on the copied url that will show up if you're connected to the same
network as your machine.
You will see a message about the page being dangerous because we're using a
self-signed certificate, you can ignore that warning and continue to the page.
Generating Builds
npm run distQuestions
For questions and support, ask on StackOverflow.
Stay in Touch
- Hang out with the community
- Follow @aframevr on X.
And get in touch with the maintainers!
Contributing
Get involved! Check out the Contributing Guide for how to get started.
You can also support development by buying a gorgeous A-Frame t-shirt with exclusive designs
License
This program is free software and is distributed under an MIT License.
Introduction Index (`docs/introduction/index.md`)
title: Introduction
section_title: Introduction
type: introduction
layout: docs
order: 1
parent_section: docs
section_order: 1
installation: true
examples:
- title: Hello, World!
src: https://glitch.com/edit/#!/aframe?path=index.html
Getting Started
A-Frame can be developed from a plain HTML file without having to install
anything. A great way to try out A-Frame is to remix the starter example on
Glitch, an online code editor that instantly hosts and deploys for
free. Alternatively, create an .html file and include A-Frame in the<head>:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>
<body>
<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>
</body>
</html>The Installation page provides more options for getting started with A-Frame.
To get started learning A-Frame, check out A-Frame School for visual
step-by-step lessons to complement the documentation.
What is A-Frame?
:a:-Frame is a web framework for building virtual reality (VR) experiences.
A-Frame is based on top of HTML, making it simple to get started. But A-Frame
is not just a 3D scene graph or a markup language; the core is a powerful
entity-component framework that provides a declarative, extensible, and
composable structure to three.js.
Originally conceived within Mozilla and now maintained by the co-creators of
A-Frame within Supermedium, A-Frame was developed to
be an easy yet powerful way to develop VR content. As an independent open
source project, A-Frame has grown to be one of the largest VR
communities.
A-Frame supports most VR and AR devices such as Meta Quest, Apple Vision Pro, PICO lineup, Lynx-R1 or Valve Index
Although A-Frame supports the whole spectrum, A-Frame aims to define
fully immersive interactive VR experiences that go beyond basic 360°
content, making full use of positional tracking and controllers.
Features
:eyeglasses: VR Made Simple: Just drop in a <script> tag and <a-scene>.
A-Frame will handle 3D boilerplate, VR setup, and default controls. Nothing to
install, no build steps.
:heart: Declarative HTML: HTML is easy to read, understand, and
copy-and-paste. Being based on top of HTML, A-Frame is accessible to everyone:
web developers, VR enthusiasts, artists, designers, educators, makers, kids.
:electric_plug: Entity-Component Architecture: A-Frame is a powerful
three.js framework, providing a declarative, composable, reusable
entity-component structure. HTML is just the tip of the iceberg;
developers have unlimited access to JavaScript, DOM APIs, three.js, WebVR, and
WebGL.
:globe_with_meridians: Cross-Platform VR: Build VR applications for Vive,
Rift, Meta Quest, Windows Mixed Reality, and Apple Vision Pro with support for
all respective controllers. Don't have a headset or controllers? No problem!
A-Frame still works on standard desktop and smartphones.
:zap: Performance: A-Frame is optimized from the ground up for WebVR. While
A-Frame uses the DOM, its elements don't touch the browser layout engine. 3D
object updates are all done in memory with little garbage and overhead. The most
interactive and large scale WebVR applications have been done in A-Frame
running smoothly at 90fps.
:mag: Visual Inspector: A-Frame provides a handy built-in visual 3D
inspector. Open up any A-Frame scene, hit <ctrl> + <alt> + i or <ctrl> + <option> + i,
and fly around to peek under the hood!
:runner: Components: Hit the ground running with A-Frame's core components
such as geometries, materials, lights, animations, models, raycasters, shadows,
positional audio, text, and controls for most major headsets. Get even further
from the hundreds of community components including environment, state, particle
systems, physics, multiuser, oceans, teleportation, super hands, and
augmented reality.
:earth_americas: Proven and Scalable: A-Frame has been used by companies
such as Google, Disney, Samsung, Toyota, Ford, Chevrolet, Amnesty
International, CERN, NPR, Al Jazeera, The Washington Post, NASA. Companies such
as Google, Microsoft, Oculus, and Samsung have made contributions to A-Frame.
Off You Go!
If it's your first time here, here's a plan for success for getting into
A-Frame:
Read through the documentation to get a grasp.
Glitch is used as a recommended coding playground
and for examples.Join us on Discord if you have any
questions, search and ask on StackOverflow,
and someone will try to get to you!When you build something, share your project online on X with the
@aframevrmention. You can also post it on the #self-promotion channel on
Supermedium Discord and #a-frame channel on
WebXR Discord.
And it really helps to have a dig into the fundamentals on JavaScript and
three.js. Have fun!
Components Anchored (`docs/components/anchored.md`)
title: anchored type: components layout: docs parent_section: components source_code: src/components/anchored.js examples: []
It requires a browser supporting the WebXR Anchors module.
Fix any entity to a position and rotation in the real world. Apply the anchored component to an entity and call the method el.components.anchored.createAnchor(position, quaternion) to anchor it to a position and rotation corresponding to real world coordinates. If creatorAnchor is not called the entity is anchored to its initial position and rotation. The anchoring only applies when in immersive mode.
Example
<a-entity id="myBox" anchored="persistent: true" geometry="primitive: box" material="color: red"></a-entity>Properties
| Properties | Description |
|---|---|
| persistent | If the anchor persists on page reloads. The entity must have an id. |
Components Animation (`docs/components/animation.md`)
title: animation
type: components
layout: docs
parent_section: components
source_code: src/components/animation.js
examples:
- title: Animating on Events
src: https://glitch.com/edit/#!/aframe-animation-events?path=index.html:1:0 - title: Generated Animations
src: https://glitch.com/~aframe-shooting-stars
The animation component lets us animate and tween values including:
- Component values (e.g.,
position,visible) - Component property values (e.g.,
light.intensity)
We can also tween values directly for better performance versus going through.setAttribute, such as by animating values:
- On the
object3D(e.g.,object3D.position.y,object3D.rotation.z) - Directly within a component (e.g.,
components.material.material.color,components.text.material.uniforms.opacity.value),
For example, translating a box:
<a-box position="-1 1.6 -5" animation="property: position; to: 1 8 -10; dur: 2000; easing: linear; loop: true" color="tomato"></a-box>Or orbiting a sphere in a 5-meter radius:
<a-entity rotation="0 0 0" animation="property: rotation; to: 0 360 0; loop: true; dur: 10000">
<a-sphere position="5 0 0" color="mediumseagreen"></a-sphere>
</a-entity>Read more below for additional options and discovering how to properly animate
different types of values.
API
Properties
| Property | Description | Default Value | Values |
|---|---|---|---|
| property | Property to animate. Can be a component name, a dot-delimited property of a component (e.g., material.color), or a plain attribute. |
||
| isRawProperty | Flag to animate an arbitrary object property outside of A-Frame components for better performance. If set to true, for example, we can set property to like components.material.material.opacity. If property starts with components or object3D, this will be inferred to true. |
false | |
| from | Initial value at start of animation. If not specified, the current property value of the entity will be used (will be sampled on each animation start). It is best to specify a from value when possible for stability. |
null | |
| to | Target value at end of animation. | null | |
| type | Right now only supports color for tweening isRawProperty color XYZ/RGB vector values. |
'' | |
| delay | How long (milliseconds) to wait before starting. | 0 | |
| dir | Which dir to go from from to to. |
normal | alternate, reverse |
| dur | How long (milliseconds) each cycle of the animation is. | 1000 | |
| easing | Easing function of animation. To ease in, ease out, ease in and out. | easeInQuad | See easings |
| elasticity | How much to bounce (higher is stronger). | 400 | |
| loop | How many times the animation should repeat. If the value is true, the animation will repeat infinitely. |
0 | |
| round | Whether to round values. | false | |
| startEvents | Comma-separated list of events to listen to trigger a restart and play. Animation will not autoplay if specified. startEvents will restart the animation, use pauseEvents to resume it. If there are other animation components on the entity animating the same property, those animations will be automatically paused to not conflict. |
null | |
| pauseEvents | Comma-separated list of events to listen to trigger pause. Can be resumed with resumeEvents. |
null | |
| resumeEvents | Comma-separated list of events to listen to trigger resume after pausing. | null | |
| autoplay | Whether or not the animation should autoplay. Should be specified if the animation is defined for the animation-timeline component. |
true | |
| enabled | If disabled, animation will stop and startEvents will not trigger animation start. | true |
Multiple Animations
The component's base name is animation. We can attach multiple animations to
one entity by name-spacing the component with double underscores (__):
<a-entity animation="property: rotation"
animation__2="property: position"
animation__color="property: material.color"></a-entity>Easings
Easings define the accelerations and speed throughout the cycle of the
animation.
| easeIn | easeOut | easeInOut | Others |
|---|---|---|---|
| easeInQuad | easeOutQuad | easeInOutQuad | linear |
| easeInCubic | easeOutCubic | easeInOutCubic | |
| easeInQuart | easeOutQuart | easeInOutQuart | |
| easeInQuint | easeOutQuint | easeInOutQuint | |
| easeInSine | easeOutSine | easeInOutSine | |
| easeInExpo | easeOutExpo | easeInOutExpo | |
| easeInCirc | easeOutCirc | easeInOutCirc | |
| easeInBack | easeOutBack | easeInOutBack | |
| easeInElastic | easeOutElastic | easeInOutElastic |
Events
| Property | Description |
|---|---|
| animationbegin | Animation began. Event detail contains name of animation. |
| animationcomplete | Animation completed. Event detail contains name of animation. |
| animationcomplete__ | Animation completed if animation has an ID (e.g., animation__click). |
Members
Accessed as el.components.animation.<MEMBER>.
| Member | Description |
|---|---|
| animation | anime.js object. |
| config | Config passed to anime.js. |
Controlling Animations using setAttribute
Like any A-Frame component, the animation component can be configured from JavaScript by calling setAttribute() on an element.
By default, the animation will begin playing immediately (autoplay is true by default).
However, care should be taken when using the interface in this way. If a finite (i.e. non-looping) animation is requested twice in a row, with identical parameters, the second animation will not play, and neither will it generate an animationcomplete event.
This is because A-Frame sees the second request as a duplicate request for configuration already applied to the element, and so the second request never reaches the animation component.
A better approach is to control the start of an animation using events (see next section)
- On initialization, configure all the animations that may be required on the object, with a custom event configured to start the animation.
- When you want to start a specific animation, do so by using emit() rather than setAttribute().
This gives more robust control of the animation (avoiding problems where you request the same animation twice in a row), and also reduces the overheads involved in setting up animation config, since it only needs to be done once.
Animating on Events
We can use the startEvents property to animate upon events:
<a-entity id="mouseCursor" cursor="rayOrigin: mouse"></a-entity>
<a-entity
geometry="primitive: box"
material="color: red"
animation__mouseenter="property: components.material.material.color; type: color; to: blue; startEvents: mouseenter; dur: 500";
animation__mouseleave="property: components.material.material.color; type: color; to: red; startEvents: mouseleave; dur: 500";
animation__customevent="property: components.material.material.color; type: color; from: red; to: blue; startEvents: triggeranimation; dur: 500";>
</a-entity>Remix the Animating on Events Glitch.
To start an animation by explicitly emitting a custom event, you can do the following:
el.emit(`triggeranimation`, null, false);The third parameter of emit set to "false" parameter ensures the event won't bubble up to parents, so that you can target the animation at just one particular element.
This assumes that an animation has already been configured to respond to that custom start event, for example like this:
el.setAttribute('animation__001', {'property': 'position',
'to': {x: 1, y: 1, z: 1},
'startEvents': 'startanim001'});(configuring startEvents on the animation automatically disables autoplay).
Animating Different Types of Values
We'll go over different cases of animating different types of values.
Component Values
We can animate a single-property component value (e.g., property: visible,
we'll go over booleans in a bit) or animate a property of a multi-property
component using a dot . as a separator (e.g., property: light.intensity,property: material.color).
If the property is a vec3, that is also supported (e.g., property: someComponent.vec3Value; to: 5 5 5).
However, animating component values this way is not the most optimal way as it
will invoke .setAttribute on each frame of the animation. For an animation
here or there, it won't be a big deal, but we can save time and memory by
animating values directly.
A special note to try not to animate values of the geometry component as that
will recreate the geometry on each tick. Rather animate scale if we want to
animate the size.
Boolean Values
We can "animate" boolean values where the to value will be applied at the end
of the animation. Like property: visible; from: false; to: true; dur: 1.
Direct Values through `object3D` and `components`
The animation component supports animating values directly through object3D
or components.
For example, we can animate values on object3D like property: object3D.position.z; to: 5 which will tween the entity's object3D.position.z
value directly without calling .setAttribute; it's the most direct way and
lets us animate a single axis at a time. Note, for object3D.rotation, degrees
are used.
Or we can animate values by reaching into components. For example, rather than
animating property: material.opacity which would call .setAttribute on each
frame, we can animate the opacity value directly with property: components.material.material.opacity. We use a dot-delimited path to walk the
object tree to find the value we want to animate, and the animation process
under the hood reduces down to changing a number.
Direct Color Values
We can animate three.js color values directly, but we'll need to specify type: color. So rather than animating property: material.color, we can doproperty: components.material.material.color; type: color.
A note on color values, we can specify color values using hex, color names,
hsl, or rgb (e.g., from: red; to: #FFCCAA or from: rgb(100, 100, 100); to: hsl(213, 100%, 70%))..
Using anime.js Directly
anime is a popular and powerful animation engine. The component prefers to do
just basic tweening and touches the surface of what anime can do (e.g.,
timelines, motion paths, progress, seeking). If we need more animation
features, create a separate component that invokes anime.js directly. anime is
accessible via AFRAME.ANIME.
Read through and explore the anime.js
documentation and
website.
See Also
Components Ar Hit Test (`docs/components/ar-hit-test.md`)
title: ar-hit-test type: components layout: docs parent_section: components source_code: src/components/scene/ar-hit-test.js examples: []
This component uses the WebXR hit-test API to position virtual objects in the real world. Remember to request the hit-test optional feature to allow it work.
You add it to the scene element and then when the user is in Augmented Reality if they tap on the screen or select with a controller it will work out where the user is pointing to in the real world and place a reticle there and fire events.
If you have set a target property to be an element it will automatically make the reticle to be the same size as the footprint of the target and then when the user selects the object will be telepoted there. It will also set the visible state of the object to true so you can hide the object until the user has placed it for the first time. The hide-on-enter-ar component is useful for that.
You can toggle this component's enabled state to not do any interactions until you are ready.
Example
<a-scene webxr="optionalFeatures: hit-test;" ar-hit-test="target:#myobject;">
<a-entity id="myobject"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| target | The object to move around. | null |
| enabled | Whether to do hit-testing or not | true |
| src | Image to use for the reticle | See: Assets |
| type | 'footprint' or 'map' footprint is the shape of the model | "footprint" |
| footprintDepth | Amount of the model used for the footprint, 1 is full height | 0.1 |
| mapSize | If no target is set then this is the size of the map | 0.5 0.5 |
Events
| Event Name | Description |
|---|---|
| ar-hit-test-start | The AR Hit Test system has managed start scanning |
| ar-hit-test-achieved | The AR Hit Test system has managed to find a surface to start hit testing on |
| ar-hit-test-select | The user has stopped positioning the detail contains the latest position, orientation and inputSource that was used |
Assets
Components Background (`docs/components/background.md`)
title: background type: components layout: docs parent_section: components source_code: src/components/scene/background.js examples: []
The background component sets a basic color background of a scene that is more
performant than a-sky since geometry is not created. There are no undesired
frustum culling issues when a-sky is further than the far plane of the
camera. There are no unexpected occlusions either with far objects that might
be behind of the sphere geometry of a-sky.
Example
The example below sets the background color to red and use lighting estimation for AR.
<a-scene background="color: red"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| color | Color of the scene background. | black |
| transparent | Background is transparent. The color property is ignored. | false |
Components Camera (`docs/components/camera.md`)
title: camera type: components layout: docs parent_section: components source_code: src/components/camera.js examples: []
The camera component defines from which perspective the user views the scene.
The camera is commonly paired with controls components that allow input devices
to move and rotate the camera.
Examples
A camera should usually be positioned at the average height of human eye level
(1.6 meters). When used with controls that receive rotation or position (e.g.
from a VR device) this position will be overridden.
<a-entity camera look-controls position="0 1.6 0"></a-entity>When moving or rotating the camera relative to the scene, use a camera rig.
By doing so, the camera's height offset can be updated by roomscale devices,
while still allowing the tracked area to be moved independently around the
scene.
<a-entity id="rig" position="25 10 0">
<a-entity id="camera" camera look-controls></a-entity>
</a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| active | Whether the camera is the active camera in a scene with more than one camera. | true |
| far | Camera frustum far clipping plane. | 10000 |
| fov | Field of view (in degrees). | 80 |
| near | Camera frustum near clipping plane. | 0.005 |
| spectator | Whether the camera is used to render a third-person view of the scene on the 2D display while in VR mode. | false |
| zoom | Zoom factor of the camera. | 1 |
Default Camera
If a camera is not specified, A-Frame will inject a default camera:
<a-entity camera="active: true" look-controls wasd-controls position="0 1.6 0" data-aframe-default-camera></a-entity>If a camera is specified (e.g., our own <a-camera> or <a-entity camera>),
then the default camera will not be added.
VR Behavior
When exiting VR, the camera will restore its rotation to its rotation before
it entered VR. This is so when we exit VR, the rotation of the camera is back
to normal for a desktop screen.
Far, near, fov, zoom properties only apply in 2D and magic window modes.
In VR mode the camera parameters are supplied by the WebVR / WebXR API to match IPD and headset FOV. Those aren't configurable.
Changing the Active Camera
When the active property gets toggled, the component will notify the camera system
to change the current camera used by the renderer:
var secondCameraEl = document.querySelector('#second-camera');
secondCameraEl.setAttribute('camera', 'active', true);Fixing Entities to the Camera
To fix entities onto the camera such that they stay within view no matter where
the user looks, you can attach those entities as a child of the camera. Use
cases might be a heads-up display (HUD).
<a-entity camera look-controls>
<a-entity geometry="primitive: plane; height: 0.2; width: 0.2" position="0 0 -1"
material="color: gray; opacity: 0.5"></a-entity>
</a-entity>Note that you should use HUDs sparingly as they cause irritation and eye strain
in VR. Consider integrating menus into the fabric of the world itself. If you
do create a HUD, make sure that the HUD is more in the center of the field of
view such that the user does not have to strain their eyes to read it.
Reading Position or Rotation of the Camera
To actively read the position or rotation of the camera, use a tick handler
of a component that reads the position or rotation, and does something with it.
Then attach the component to the camera.
AFRAME.registerComponent('rotation-reader', {
tick: function () {
// `this.el` is the element.
// `object3D` is the three.js object.
// `rotation` is a three.js Euler using radians. `quaternion` also available.
console.log(this.el.object3D.rotation);
// `position` is a three.js Vector3.
console.log(this.el.object3D.position);
}
});
// <a-entity camera look-controls rotation-reader>Reading World Position or Rotation of the Camera
three.js has methods to attain position or rotation (or scale) in world space
versus object local space.
AFRAME.registerComponent('rotation-reader', {
/**
* We use IIFE (immediately-invoked function expression) to only allocate one
* vector or euler and not re-create on every tick to save memory.
*/
tick: (function () {
var position = new THREE.Vector3();
var quaternion = new THREE.Quaternion();
return function () {
this.el.object3D.getWorldPosition(position);
this.el.object3D.getWorldQuaternion(quaternion);
// position and rotation now contain vector and quaternion in world space.
};
})()
});Components Cursor (`docs/components/cursor.md`)
title: cursor
type: components
layout: docs
parent_section: components
source_code: src/components/cursor.js
examples:
- title: Mouse Click Example
src: https://glitch.com/edit/#!/a-frame-mouse-click-example
The cursor component provides hover and click states for interaction on top of
the raycaster component. The cursor component can be used for
both gaze-based and controller-based interactions, but the appearance needs
to be configured depending on the use case. The <a-cursor>
primitive provides a default reticle appearance for a gaze-based
cursor, and the laser-controls component configures the
cursor for all controllers.
The cursor component listens to events and keeps state on what's being hovered
and pressed in order to provide mousedown, mouseup, mouseenter,mouseleave, and click events. We use the name mouse to mimic
traditional web development for now. Under the hood, the cursor component uses
the raycaster-intersection and raycaster-intersection-cleared events,
capturing the closest visible intersected entity.
By default, the cursor is configured to be used in a gaze-based mode and will
register user input via mouse or touch. Specifying the downEvents andupEvents properties allows the cursor to work with controllers. For example,
the laser-controls component automatically configures these
properties to work with most controllers.
To provide a shape or appearance to the cursor, we should apply either the
geometry and material components or use the raycaster
component's showLine property to draw a line using the line
component.
Example
For example, we can create a ring-shaped cursor fixed to the center of the
screen. To fix the cursor to the screen so the cursor is always present no
matter where we look, we place it as a child of the active camera
entity. We pull it in front of the camera by placing it on the negative Z axis.
When the cursor clicks on the box, we can listen to the click event.
<a-entity camera look-controls>
<a-entity cursor="fuse: true; fuseTimeout: 500"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: black; shader: flat">
</a-entity>
</a-entity>
<a-entity id="box" cursor-listener geometry="primitive: box" material="color: blue"></a-entity>// Component to change to a sequential color on click.
AFRAME.registerComponent('cursor-listener', {
init: function () {
var lastIndex = -1;
var COLORS = ['red', 'green', 'blue'];
this.el.addEventListener('click', function (evt) {
lastIndex = (lastIndex + 1) % COLORS.length;
this.setAttribute('material', 'color', COLORS[lastIndex]);
console.log('I was clicked at: ', evt.detail.intersection.point);
});
}
});Properties
| Property | Description | Default Value |
|---|---|---|
| downEvents | Array of additional events on the entity to listen to for triggering mousedown (e.g., triggerdown for vive-controls). |
[] |
| fuse | Whether cursor is fuse-based. | false on desktop, true on mobile |
| fuseTimeout | How long to wait (in milliseconds) before triggering a fuse-based click event. | 1500 |
| hand | Filter WebXR selectstart/selectend events by controller handedness (left or right). When set, the cursor only responds to WebXR events from the matching controller. Used by laser-controls to prevent one controller's trigger from affecting another controller's hovered entity. |
'' |
| mouseCursorStylesEnabled | Whether to show pointer cursor in rayOrigin: mouse mode when hovering over entity. |
true |
| rayOrigin | Where the intersection ray is cast from (i.e. xrselect ,entity or mouse). rayOrigin: mouse is extremely useful for VR development on a mouse and keyboard. |
entity |
| upEvents | Array of additional events on the entity to listen to for triggering mouseup. |
[] |
To further customize the cursor component, we configure the cursor's dependency
component, the raycaster component.
Events
| Event | Description |
|---|---|
| click | Emitted on both cursor and intersected entity if a currently intersected entity is clicked (whether by mouse or by fuse). |
| fusing | Emitted on both cursor and intersected entity when fuse-based cursor starts counting down. |
| mousedown | Emitted on both cursor and intersected entity (if any) on mousedown on the canvas element. |
| mouseenter | Emitted on both cursor and intersected entity (if any) when cursor intersects with an entity. |
| mouseleave | Emitted on both cursor and intersected entity (if any) when cursor no longer intersects with previously intersected entity. |
| mouseup | Emitted on both cursor and intersected entity (if any) on mouseup on the canvas element. |
Event Data
Additional detail is included in the detail object on the event as follows:
intersection
Relevant events will contain in the event detail intersection, which will
contain {distance, point, face, faceIndex, indices, object} about specific
data about the intersection:
this.el.addEventListener('click', function (evt) {
console.log(evt.detail.intersection.point);
});intersectedEl
Events emitted on the cursor entity also include event detail intersectedEl, which provides a reference to the intersected entity.
mouseEvent and touchEvent
Where the trigger for a cursor event is a MouseEvent or TouchEvent, event detail mouseEvent or touchEvent provides a reference to that event.
These events provide a wealth of additional detail about the event, as detailed in the APIs linked above. Among other things, they can indicate:
- which mouse button was used
- information about the state of the mouse buttons (or multi-touch for touch events), and relevant keys shuch as Shift, Ctrl etc. at the time of the mouse or touch event.
- the screen co-ordinates where the event occurred.
This information can be used by applications to handle cursor events differently, depending on this information (e.g. different handling of left click & right click).
For example:
this.el.addEventListener('click', function (evt) {
if (!evt.detail.mouseEvent || evt.detail.mouseEvent.button === 0) {
console.log("left button clicked (or touch event / no information)");
} else if (evt.detail.mouseEvent.button === 2) {
console.log("right button clicked");
}
});At most one of mouseEvent or touchEvent will be present on a cursor event, and sometimes neither will. Neither will be present on mousenter, mouseleave and fusing events, nor on a click event that has been triggered by a fuse timeout rather than a mouse click or touch event.
States
The cursor will add states to the cursor entity on certain events:
| State | Description |
|---|---|
| cursor-fusing | Added when the cursor is fusing on another entity. |
| cursor-hovering | Added when the cursor is hovering over another entity. |
The cursor will add states to intersected entities on certain events:
| State | Description |
|---|---|
| cursor-hovered | Added to the intersected entity when the cursor is hovering over it. |
Configuring the Cursor through the Raycaster Component
The cursor builds on top of and depends on the raycaster component. If we
want to customize the raycasting pieces of the cursor, we can do by changing
the raycaster component properties. Say we want set a max
distance, check for intersections less frequently, and set which objects are
clickable:
<a-entity cursor raycaster="far: 20; interval: 1000; objects: .clickable"></a-entity>Fuse-Based Cursor
Also known as gaze-based cursor. If we set the cursor to be fuse-based, the
cursor will trigger a click if the user gazes at an entity for a set amount of
time. Imagine a laser strapped to the user's head, and the laser extends out
into the scene. If the user stares at an entity long enough (i.e., thefuseTimeout), then the cursor will trigger a click.
The advantage of fuse-based interactions for VR is that it does not require
extra input devices other than the headset. The disadvantage of fuse-based
interactions is that it requires the user to turn their head a lot.
Adding Visual Feedback
To add visual feedback to the cursor to show when the cursor is clicking or
fusing, we can use the animation component. When the cursor
intersects the entity, it will emit an event, and the animation system will
pick up event with the begin attribute:
<a-entity
animation__click="property: scale; startEvents: click; easing: easeInCubic; dur: 150; from: 0.1 0.1 0.1; to: 1 1 1"
animation__fusing="property: scale; startEvents: fusing; easing: easeInCubic; dur: 1500; from: 1 1 1; to: 0.1 0.1 0.1"
animation__mouseleave="property: scale; startEvents: mouseleave; easing: easeInCubic; dur: 500; to: 1 1 1"
cursor="fuse: true;"
material="color: black; shader: flat"
position="0 0 -3"
geometry="primitive: ring"></a-entity>To play with an example of a cursor with visual feedback, check out the Cursor
with Visual Feedback example on CodePen.
XR Select Cursor
When an XR "selectstart" event happens the raycaster picks an element based upon it's current location.
This works for handheld AR, and headmounted VR and AR. This works well with the mouse rayOrigin too.
<a-scene
cursor__mouse="rayOrigin: mouse"
cursor__xrselect="rayOrigin: xrselect"
raycaster="objects:#objects *;"
>Components Debug (`docs/components/debug.md`)
title: debug type: components layout: docs parent_section: components source_code: src/components/scene/debug.js
The debug component enables component-to-DOM serialization.
Example
<a-scene debug></a-scene>Component-to-DOM Serialization
By default, for performance reasons, A-Frame does not update the DOM with
component data. This also means mutation observers will not fire. If we open
the browser's DOM inspector, we will see only the component names (and not the
values) are visible.
<a-entity geometry material position rotation></a-entity>A-Frame stores the component data in memory. Updating the DOM takes CPU time
for converting internal component data to strings. If we want to see the DOM
update for debugging purposes, we can attach the debug component to the
scene. Components will check for an enabled debug component before trying to
serialize to the DOM. Then we will be able to view component data in the DOM:
<a-entity geometry="primitive: box" material="color: red" position="1 2 3" rotation="0 180 0"></a-entity>Make sure that this component is not active in production.
Manually Serializing to DOM
To manually serialize to DOM, use Entity.flushToDOM orComponent.flushToDOM:
document.querySelector('a-entity').components.position.flushToDOM(); // Flush a component.
document.querySelector('a-entity').flushToDOM(); // Flush an entity.
document.querySelector('a-entity').flushToDOM(true); // Flush an entity and its children.
document.querySelector('a-scene').flushToDOM(true); // Flush every entity.Components Device Orientation Permission Ui (`docs/components/device-orientation-permission-ui.md`)
title: device-orientation-permission-ui type: components layout: docs parent_section: components source_code: src/components/scene/device-orientation-permission-ui.js
Starting with Safari on iOS 13 browsers require sites to be served over https and request user permission to access DeviceOrientation events. This component presents a permission dialog for the user to grant or deny access.
The device-orientation-permission-ui component applies only to the <a-scene> element
To configure the style of the dialog one can redefine the associated css styles. To change the colors of the allow, deny and ok buttons:
.a-dialog-allow-button {
background-color: red;
}
.a-dialog-deny-button {
background-color: blue;
}
.a-dialog-ok-button {
background-color: green;
}The dialog can also be disabled all together if you prefer to handle the permissions workflow differently.
Example
<a-scene device-orientation-permission-ui="enabled: false"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| enabled | Whether or not to display the dialog when required | true |
| denyButtonText | Deny button text | English text |
| allowButtonText | Allow button text | English text |
| cancelButtonText | Cancel button text | English text |
| deviceMotionMessage | Message in dialog requesting user permission to enable the Device Motion API. | English text |
| mobileDesktopMessage | Message displayed in modal requesting user to switch to mobile browsing mode. | English text |
| httpsMessage | Message requesting user to switch to HTTPS. | English text |
Events
| Event Name | Description |
|---|---|
| deviceorientationpermissiongranted | User has granted access to DeviceOrientation events |
| deviceorientationpermissionrejected | User or browser has denied access to DeviceOrientation events |
| deviceorientationpermissionrequested | Application has requested permission to access DeviceOrientation events |
Components Embedded (`docs/components/embedded.md`)
title: embedded type: components layout: docs parent_section: components source_code: src/components/scene/embedded.js examples: []
The embedded component removes fullscreen CSS styles from A-Frame's<canvas> element, making it easier to embed within the layout of an
existing webpage. Embedding removes the default fixed positioning from
the canvas and makes the Enter VR button smaller.
Example
Add the embedded component, then style the <a-scene> element with CSS as if
it were an empty ``. Within <a-scene> is the <canvas> element.
a-scene {
height: 300px;
width: 600px;
}
<body>
<div id="myEmbeddedScene">
<a-scene embedded>
</a-scene>
</div>
</body>An inline example of an embedded scene:
Integration with the 2D Page
Embedded scenes can be visually integrated and can interact with the rest of
the 2D page. Some things we could do:
- Use CSS
z-indexto put an HTML element above or under the scene - Use a transparent
<a-sky>or no background to overlay<a-scene>on top of the page, with underlying HTML elements visible - Use interaction with HTML elements (e.g., buttons, forms) to affect the scene
Using I-Frames
Only one <a-scene> can exist on a page. Alternatively, we can use an
`` with allowfullscreen="yes" and allowvr="yes" to display multiple
scenes. For now, if the I-Frame is not on the same origin as the page, the
WebVR polyfill for mobile won't work and there won't be any tracked rotation of
the device.
Components Fog (`docs/components/fog.md`)
title: fog type: components layout: docs parent_section: components source_code: src/components/scene/fog.js examples: []
The fog component obscures entities in fog given distance from the camera. The
fog component applies only to the <a-scene> element.
Example
<a-scene fog="type: linear; color: #AAA"></a-scene>Properties
Given the fog distribution type, different properties will apply.
| Property | Description | Default Value |
|---|---|---|
| type | Type of fog distribution. Can be linear or exponential. |
linear |
| color | Color of fog. For example, if set to black, far away objects will be rendered black. | #000 |
Linear Fog
Linear fog grows linearly denser with distance.
| Property | Description | Default Value |
|---|---|---|
| near | Minimum distance to start applying fog. Objects closer than this won't be affected by fog. | 1 |
| far | Maximum distance to stop applying fog. Objects farther than this won't be affected by fog. | 1000 |
Exponential Fog
Exponential fog grows exponentially denser with distance.
| Property | Description | Default Value |
|---|---|---|
| density | How quickly the fog grows dense. | 0.00025 |
Excluding a Material from Fog
To not apply fog to certain entities, we can disable fog for certain materials.
<a-entity material="fog: false"></a-entity>Components Geometry (`docs/components/geometry.md`)
title: geometry
type: components
layout: docs
parent_section: docs
section_title: Components
section_order: 4
source_code: src/components/geometry.js
examples:
- title: Creating Shapes
src: https://glitch.com/edit/#!/ex-2-geometry?path=index.html:1:0 - title: Texture on Shapes
src: https://glitch.com/edit/#!/ex-2a-texture-on-shape?path=index.html:1:0 - title: Shape as Entity
src: https://glitch.com/edit/#!/ex-2b-shape-as-entity?path=index.html:1:0 - title: Animating Shapes
src: https://glitch.com/edit/#!/ex-2c-animating-shapes?path=index.html:1:0 - title: Animating Shapes with Components
src: https://glitch.com/edit/#!/ex-2d-animating-shapes-with-components?path=index.html:1:0
The geometry component provides a basic shape for an entity. The primitive
property defines the general shape. Geometric primitives, in computer graphics,
are irreducible basic shapes. A material component is commonly defined to
provide an appearance alongside the shape to create a complete mesh.
Overview of available geometries:
Base Properties
Every geometry type will have these properties:
| Property | Description | Default Value |
|---|---|---|
| primitive | Name of a geometry (e.g., one of the geometries listed below). Determines the geometry type and what other properties are available. | box |
| skipCache | Disable retrieving the shared geometry object from the cache. | false |
Built-in Geometries
`box`
The box geometry defines boxes (i.e., any quadrilateral, not just cubes).
<a-entity geometry="primitive: box; width: 1; height: 1; depth: 1"></a-entity>| Property | Description | Default Value |
|---|---|---|
| width | Width (in meters) of the sides on the X axis. | 1 |
| height | Height (in meters) of the sides on the Y axis. | 1 |
| depth | Depth (in meters) of the sides on the Z axis. | 1 |
| segmentsDepth | Number of segmented faces on the z-axis | 1 |
| segmentsHeight | Number of segmented faces on the y-axis | 1 |
| segmentsWidth | Number of segmented faces on the x-axis | 1 |
`circle`
The circle geometry creates flat two-dimensional circles. These can be complete
circles or partial circles (like Pac-Man). Note that because circles are flat,
A-Frame will only render a single face of the circle if we don't specify side: double on the material component.
<a-entity geometry="primitive: circle; radius: 1" material="side: double"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius (in meters) of the circle. | 1 |
| segments | Number of triangles to construct the circle, like pizza slices. A higher number of segments means the circle will be more round. | 32 |
| thetaStart | Start angle for first segment. Can be used to define a partial circle. | 0 |
| thetaLength | The central angle (in degrees). Defaults to 360, which makes for a complete circle. |
360 |
`thetaLength` and `thetaStart` Properties
In degrees, thetaStart defines where to start a circle or arc andthetaLength defines where a circle or arc ends. If we wanted to make a (
shape, we would start the circle halfway through and define the length as half
of a circle. We can do this with thetaStart: 180; thetaLength: 180. Or if we
wanted to make a ) shape, we can do thetaStart: 0; thetaLength: 180.
Useful cases might be to animating thetaStart to create a spinner effect or
animating thetaLength on a fuse-based cursor for visual feedback.
`cone`
The cone geometry is a cylinder geometry that have different top and bottom radii.
<a-entity geometry="primitive: cone; radiusBottom: 1; radiusTop: 0.1"></a-entity>| Property | Description | Default Value |
|---|---|---|
| height | Height of the cone. | 2 |
| openEnded | Whether the ends of the cone are open (true) or capped (false). | false |
| radiusBottom | Radius of the bottom end of the cone. | 1 |
| radiusTop | Radius of the top end of the cone. | 1 |
| segmentsRadial | Number of segmented faces around the circumference of the cone. | 36 |
| segmentsHeight | Number of rows of faces along the height of the cone. | 18 |
| thetaStart | Starting angle in degrees. | 0 |
| thetaLength | Central angle in degrees. | 360 |
`cylinder`
The cylinder geometry creates cylinders in the traditional sense like a
Coca-Cola™ can, but it can also define shapes such as tubes and curved
surfaces.
We can create a basic cylinder using height and radius:
<a-entity geometry="primitive: cylinder; height: 3; radius: 2"></a-entity>We can create a tube by making the cylinder open-ended, which removes the top
and bottom surfaces of the cylinder such that the inside is visible. Then we
need a double-sided material to render properly:
<a-entity geometry="primitive: cylinder; openEnded: true" material="side: double"></a-entity>We can create a curved surfaces by specifying the arc via thetaLength such
that the cylinder doesn't curve all the way around, making the cylinder
open-ended, and then making the material double-sided:
<a-entity geometry="primitive: cylinder; openEnded: true; thetaLength: 180"
material="side: double"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius of the cylinder. | 1 |
| height | Height of the cylinder. | 2 |
| segmentsRadial | Number of segmented faces around the circumference of the cylinder. | 36 |
| segmentsHeight | Number of rows of faces along the height of the cylinder. | 18 |
| openEnded | Whether the ends of the cylinder are open (true) or capped (false). | false |
| thetaStart | Starting angle in degrees. | 0 |
| thetaLength | Central angle in degrees. | 360 |
We can create prisms by changing the number of radial segments
(i.e., sides). For example, to make a hexagonal prism:
<a-entity geometry="primitive: cylinder; segmentsRadial: 6"></a-entity>`dodecahedron`
The dodecahedron geometry creates a polygon with twelve equally-sized faces.
<a-entity geometry="primitive: dodecahedron; radius: 2"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius (in meters) of the dodecahedron. | 1 |
`octahedron`
The octahedron geometry creates a polygon with eight equilateral triangular faces.
<a-entity geometry="primitive: octahedron"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius (in meters) of the octahedron. | 1 |
`icosahedron`
The icosahedron geometry creates a polygon with twenty equilateral triangular faces.
<a-entity geometry="primitive: icosahedron"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius (in meters) of the icosahedron. | 1 |
`plane`
The plane geometry creates a flat surface. Because planes are flat, A-Frame
will render only a single face of the plane unless we specify side: double on
the material component.
<a-entity geometry="primitive: plane; height: 10; width: 10" material="side: double"></a-entity>| Property | Description | Default Value |
|---|---|---|
| width | Width along the X axis. | 1 |
| height | Height along the Y axis. | 1 |
| segmentsHeight | Number of segmented faces on the y-axis | 1 |
| segmentsWidth | Number of segmented faces on the x-axis | 1 |
`ring`
The ring geometry creates a flat ring, like a CD. Because the ring is
flat, A-Frame will only render a single face of the ring unless we specify side: double the material component.
<a-entity geometry="primitive: ring; radiusInner: 0.5; radiusOuter: 1"
material="side: double"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radiusInner | Radius of the inner hole of the ring. | 1 |
| radiusOuter | Radius of the outer edge of the ring. | 1 |
| segmentsTheta | Number of segments. A higher number means the ring will be more round. | 32 |
| segmentsPhi | Number of triangles within each face defined by segmentsTheta. | 8 |
| thetaStart | Starting angle in degrees. | 0 |
| thetaLength | Central angle in degrees. | 360 |
`sphere`
The sphere geometry creates spheres (e.g., balls). We can create a basic sphere:
<a-entity geometry="primitive: sphere; radius: 2"></a-entity>We can create polyhedrons and abstract shapes by specifying the number of
horizontal angles and faces:
<a-entity geometry="primitive: sphere; segmentsWidth: 2; segmentsHeight: 8"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius of the sphere. | 1 |
| segmentsWidth | Number of horizontal segments. | 18 |
| segmentsHeight | Number of vertical segments. | 36 |
| phiStart | Horizontal starting angle. | 0 |
| phiLength | Horizontal sweep angle size. | 360 |
| thetaStart | Vertical starting angle. | 0 |
| thetaLength | Vertical sweep angle size. | 360 |
`tetrahedron`
The tetrahedron geometry creates a polygon with four triangular faces.
<a-entity geometry="primitive: tetrahedron; radius: 2"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius (in meters) of the tetrahedron. | 1 |
`torus`
The torus geometry creates a donut or curved tube shape:
<a-entity geometry="primitive: torus; radius: 2; radiusTubular: 0.5; arc: 180"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius of the outer edge of the torus. | 1 |
| radiusTubular | Radius of the tube. | 0.2 |
| segmentsRadial | Number of segments along the circumference of the tube ends. A higher number means the tube will be more round. | 36 |
| segmentsTubular | Number of segments along the circumference of the tube face. A higher number means the tube will be more round. | 32 |
| arc | Central angle. | 360 |
`torusKnot`
The torus knot geometry creates a pretzel shape. A pair of coprime integers,p and q, defines the particular shape of the pretzel. If p and q are
not coprime the result will be a torus link:
<a-entity geometry="primitive: torusKnot; p: 3; q:7"></a-entity>| Property | Description | Default Value |
|---|---|---|
| radius | Radius that contains the torus knot. | 1 |
| radiusTubular | Radius of the tubes of the torus knot. | 0.2 |
| segmentsRadial | Number of segments along the circumference of the tube ends. A higher number means the tube will be more round. | 36 |
| segmentsTubular | Number of segments along the circumference of the tube face. A higher number means the tube will be more round. | 32 |
| p | How many times the geometry winds around its axis of rotational symmetry. | 2 |
| q | How many times the geometry winds around a circle in the interior of the torus. | 3 |
`triangle`
The triangle geometry creates a flat two-dimensional triangle. Because triangles are flat,
A-Frame will render only a single face, which is the one with vertexA, vertexB, andvertexC appear in counterclockwise order on the screen, unless we specify side: double on
the material component.
<a-entity geometry="primitive: triangle" material="side: double"></a-entity>| Property | Description | Default Value |
|---|---|---|
| vertexA | Coordinates of one of the three vertices | 0 0.5 0 |
| vertexB | Coordinates of one of the three vertices | -0.5 -0.5 0 |
| vertexC | Coordinates of one of the three vertices | 0.5 -0.5 0 |
Register a Custom Geometry
We can register our own geometries using AFRAME.registerGeometry and creating
an object that is an instance of THREE.BufferGeometry.
Recent versions of three.js rename generators such as PlaneBufferGeometry to just
PlaneGeometry,
but support the old name as an alias. See the three.js manual to learn about creating a
custom BufferGeometry.
A-Frame
registers all built-in geometries using this API. Here is how A-Frame registers
the box geometry:
AFRAME.registerGeometry('box', {
schema: {
depth: {default: 1, min: 0},
height: {default: 1, min: 0},
width: {default: 1, min: 0},
segmentsHeight: {default: 1, min: 1, max: 20, type: 'int'},
segmentsWidth: {default: 1, min: 1, max: 20, type: 'int'},
segmentsDepth: {default: 1, min: 1, max: 20, type: 'int'}
},
init: function (data) {
this.geometry = new THREE.BoxGeometry(data.width, data.height, data.depth);
}
});Like with registering components, we provide a name, a
schema that will expose the properties of the geometry, and
lifecycle methods. Then we need to create the geometry and set onthis.geometry through the init lifecycle method.
When a geometry component sets its primitive property to the custom geometry
name, we can set the properties of the custom geometry on the geometry
component. Say we registered a custom geometry:
AFRAME.registerGeometry('example', {
schema: {
vertices: {
default: ['-10 10 0', '-10 -10 0', '10 -10 0'],
}
},
init: function (data) {
var geometry = new THREE.BufferGeometry();
const pointsArray = new Array();
data.vertices.map(function (vertex) {
var points = vertex.split(' ').map(function(x){return parseInt(x);});
pointsArray.push(new THREE.Vector3(points[0], points[1], points[2]));
});
geometry.setFromPoints(pointsArray);
geometry.computeBoundingBox();
geometry.computeVertexNormals();
this.geometry = geometry;
}
});We can then use that custom geometry in HTML:
<a-entity geometry="primitive: example; vertices: 1 1 -3, 3 1 -3, 2 2 -3"></a-entity>Components Gltf Model (`docs/components/gltf-model.md`)
title: gltf-model
type: components
layout: docs
parent_section: components
source_code: src/components/gltf-model.js
examples:
- title: Modifying Material of Model
src: https://aframe.io/aframe/examples/test/gltf-model/modify-materials.html
glTF (GL Transmission Format) is an open project by Khronos
providing a common, extensible format for 3D assets that is both efficient and
highly interoperable with modern web technologies.
The gltf-model component loads a 3D model using a glTF (.gltf or .glb)
file.
Note that glTF is a fairly new specification and adoption is still growing.
Work on the three.js glTF loader and converters are still
active.
NOTE: A-Frame supports glTF 2.0. For models using older versions of the
glTF format, usegltf-model-legacyfrom aframe-extras.
Why use glTF?
In comparison to the older OBJ format, which supports only
vertices, normals, texture coordinates, and basic materials, glTF provides a
more powerful set of features. In addition to all of the above, glTF offers:
- Hierarchical objects
- Scene information (light sources, cameras)
- Skeletal structure and animation
- More robust materials and shaders
For simple models with no animation, OBJ is nevertheless a common and reliable
choice.
In comparison to COLLADA or FBX, the supported features are very
similar. However, because glTF focuses on providing a "transmission format"
rather than an editor format, it is more interoperable with web technologies.
By analogy, the .PSD (Adobe Photoshop) format is helpful for editing 2D images,
but images are converted to .JPG for use on the web. In the same way, glTF is a
simpler way of transmitting 3D assets while rendering the same result.
In short, expect glTF models to work with A-Frame more reliably than other
formats.
Example
Load a glTF model by pointing to an asset that specifies the src for a glTF
file.
<a-scene>
<a-assets>
<a-asset-item id="tree" src="/path/to/tree.gltf"></a-asset-item>
</a-assets>
<a-entity gltf-model="#tree"></a-entity>
</a-scene>Values
| Type | Description |
|---|---|
| selector | Selector to an <a-asset-item> |
| string | url()-enclosed path to a glTF file |
| Data URL | data:model/gltf-binary;base64,... |
Data URL can be used for small glb files if you want to embed the assets in
the html page. You can create a Data URL with those commands:
echo -n "data:model/gltf-binary;base64," >/tmp/base64.txt
base64 -w 0 model.glb >>/tmp/base64.txtEvents
| Event Name | Description |
|---|---|
| model-loaded | glTF model has been loaded into the scene. |
| model-error | glTF model could not be loaded. |
Loading Inline
Alternatively, load a glTF model by specifying the path directly withinurl(). However, the scene won't wait for the resource to load before
rendering.
<a-entity gltf-model="url(/path/to/tree.gltf)"></a-entity>Using animations
If you want to use the animations from your glTF model, you can use the animation-mixer component from aframe-extras. By default all animations are played in a loop.
<a-entity gltf-model="#monster" animation-mixer></a-entity>Using compression
glTF file size may be reduced using Draco or Meshopt compression. Neither of these affect textures, which should be compressed or resized by other methods. Furthermore, compression does not particularly affect framerate — if the model has too many triangles or draw calls, compression will not change that, and the model should be simplified using tools like Blender or gltfpack, instead.
- Draco: Compression for geometry, often reducing geometry size by 90-95%. Requires some extra time to decompress on the device, but this occurs off the main thread in a Web Worker.
- Meshopt: Compression for geometry, morph targets, and animation. If combined with additional lossless compression (like gzip) Meshopt may have similar compression ratios to Draco, with much faster decompression. Note: Some web servers do not support gzip with
.glbor.gltffiles (see: GitHub Pages).
To optimize an existing glTF model, use tools such as:
- Blender for Draco compression
- glTF-Pipeline for Draco compression
- glTF-Transform for Draco or Meshopt compression
- gltfpack for Meshopt compression
You'll also need to load a decoder library by configuring scene properties as explained below.
Scene properties
When using glTF models compressed with Draco, KTX2 or Meshopt, you must configure the path to the necessary decoders:
<a-scene gltf-model="dracoDecoderPath: path/to/decoder/;
basisTranscoderPath: path/to/transcoder/;
meshoptDecoderPath: path/to/meshopt_decoder.cjs;">
<a-entity gltf-model="url(pony.glb)"></a-entity>
</a-scene>| Property | Description | Default Value |
|---|---|---|
| dracoDecoderPath | Path to the Draco decoder libraries. | 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/' |
| basisTranscoderPath | Path to the basis/KTX2 transcoder libraries. | '' |
| meshoptDecoderPath | Path to the Meshopt decoder. | '' |
dracoDecoderPath path must be a folder containing three files:
draco_decoder.js— Emscripten-compiled decoder, compatible with old browsers like IE11.draco_decoder.wasm— WebAssembly decoder, compatible with modern browsers.draco_wasm_wrapper.js— JavaScript wrapper for the WASM decoder.
These files are available from the three.js repository, under
examples/jsm/libs/draco/gltf, but a more recent version may
exist in the draco repository.
The gltf-model component will automatically choose whether to use a WASM or JavaScript
decoder, so both should be included.
basisTranscoderPath path must be a folder containing two files:
basis_transcoder.js — JavaScript wrapper for the WebAssembly transcoder.
basis_transcoder.wasm — WebAssembly transcoder.These files are available from the three.js repository in /examples/jsm/libs/basis.
You can use a CDN like this: basisTranscoderPath:https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/basis/;
meshoptDecoderPath path should be the complete file path (including filename) for a Meshopt decoder, typically named meshopt_decoder.cjs. Meshopt requires WebAssembly support. A CDN-hosted, versioned decoder is available at https://unpkg.com/[email protected]/meshopt_decoder.cjs (check the latest version number on npm), or you may download copies from the meshoptimizer GitHub repository.
More Resources
Over 100,000 glTF models are free for download on Sketchfab, and
various exporters and converters converters are available:
See the official glTF specification for available features, community
resources, and ways to contribute.
Components Hand Controls (`docs/components/hand-controls.md`)
title: hand-controls type: components layout: docs parent_section: components source_code: src/components/hand-controls.js examples: []
The hand-controls component provides tracked hands (using a prescribed model)
with animated gestures. hand-controls wraps the vive-controls and
meta-touch-controls which in turn wrap the tracked-controls
component. By specifying just hand-controls, we have something that
works well with both Vive and Rift. The component gives extra events and
handles hand animations and poses.
Example
<a-entity id="leftHand" hand-controls="hand: left; handModelStyle: lowPoly; color: #ffcccc"></a-entity>
<a-entity id="rightHand" hand-controls="hand: right; handModelStyle: lowPoly; color: #ffcccc"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| color | Color of hand material. | white |
| hand | Associated controller. Can be left or right. |
left |
| handModelStyle | Style of the hand 3D model loaded. Can be lowPoly, highPoly or toon. |
lowPoly |
Events
| Event Name | Description |
|---|---|
| gripdown | The hand is closed into a fist without thumb raised. |
| gripup | The hand is no longer closed into a fist without thumb raised. |
| pointup | The hand is touching or pressing the trigger only. |
| pointdown | The hand is no longer touching or pressing the trigger only. |
| thumbup | The hand is closed into a fist with thumb raised. |
| thumbdown | The hand is no longer closed into a fist with thumb raised. |
| pointingstart | The hand is pointing with index finger without thumb raised. |
| pointingend | The hand is no longer pointing without thumb raised. |
| pistolstart | The hand is pointing with index finger and thumb raised. |
| pistolend | The hand is no longer pointing with thumb raised. |
Assets
Components Hand Tracking Controls (`docs/components/hand-tracking-controls.md`)
title: hand-tracking-controls type: components layout: docs parent_section: components source_code: src/components/hand-tracking-controls.js examples: []
Use hand-tracking-controls to integrate hand tracked input in your application. The component provides a visual representation of the hand and basic gesture recognition. It can be used along tracked controllers (e.g: meta-touch-controls) for applications requiring multiple input methods. Component is only active when the browser and underlying system starts tracking the user's hands.
Example
<a-entity id="leftHand" hand-tracking-controls="hand: left;"></a-entity>
<a-entity id="rightHand" hand-tracking-controls="hand: right;"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| hand | The hand that will be tracked (i.e., right, left). | left |
| modelColor | Color of hand material. | white |
| modelOpacity | Opacity of the hand material. | 1.0 |
| modelStyle | Mesh representing the hand or dots matching the joints | mesh |
Events
| Event Name | Description |
|---|---|
| pinchstarted | The pinch gesture has started. World coordinates passed as event detail. |
| pinchended | The pinch gesture has ended. World coordinates passed as event detail. |
| pinchmoved | The hand moved while making the pinch gesture. Useful for interactions that track the hand while the gesture is engaged. World coordinates passed as event detail. |
Assets
Components Hand Tracking Grab Controls (`docs/components/hand-tracking-grab-controls.md`)
title: hand-tracking-grab-controls type: components layout: docs parent_section: components source_code: src/components/hand-tracking-grab-controls.js examples: []
Grab any entity with hand tracking using the pinch gesture.
Example
<a-entity id="leftHand" hand-tracking-grab-controls="hand: left;"></a-entity>
<a-entity id="rightHand" hand-tracking-grab-controls="hand: right;"></a-entity>The grabbable component makes any entity hand grababble.
<a-box grabbable></a-box>For debugging purposes you can make the colliders visible, changing color when a collision is detected, as shown below:
<a-scene obb-collider="showColliders: true"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| hand | The hand that will be tracked (i.e., right, left). | left |
| color | The color of the hand model. | white |
| hoverColor | Hand color when hand intersects a grabbable entity bounding box. | #538df1 |
| hoverEnabled | If the hand model changes color when intersecting a grabbable entity. | false |
Components Hide On Enter Ar (`docs/components/hide-on-enter-ar.md`)
title: hide-on-enter-ar type: components layout: docs parent_section: components source_code: src/components/hide-on-enter-ar.js examples: []
When the user enters AR this component will hide the component by toggling it's visible state.
This is used to hide background elements such as floors, sky boxes, environments and other large elements. Letting the user comfortably fit the remaining visible elements into their physical space.
You can also use this to disable scene lighting if you will be using lighting estimation instead.
Example
<a-light hide-on-enter-ar></a-light>
<a-sky hide-on-enter-ar color="skyblue"></a-sky>Properties
None
Events
None
Components Hide On Enter Vr (`docs/components/hide-on-enter-vr.md`)
title: hide-on-enter-vr type: components layout: docs parent_section: components source_code: src/components/hide-on-enter-vr.js examples: []
When the user enters VR this component will hide the entity by toggling it's visible state.
Depending on the immersive mode: VR or AR one might want to show / hide different entities.
Example
<a-entity hide-on-enter-vr></a-entity>Properties
None
Events
None
Components Keyboard Shortcuts (`docs/components/keyboard-shortcuts.md`)
title: keyboard-shortcuts type: components layout: docs parent_section: components source_code: src/components/scene/keyboard-shortcuts.js
The keyboard-shortcuts component toggles global keyboard shortcuts. The
keyboard-shortcuts component applies only to the <a-scene> element.
Example
<a-scene keyboard-shortcuts="enterVR: false; exitVR: false;"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| enterVR | Enables the shortcut to press 'F' to enter VR. | true |
| exitVR | Enables the shortcut to press 'Escape' to exit VR. | true |
Components Laser Controls (`docs/components/laser-controls.md`)
title: laser-controls type: components layout: docs parent_section: components image: src: https://user-images.githubusercontent.com/674727/26897482-91947a94-4b7d-11e7-9cb5-5c47f50938e4.gif source_code: src/components/laser-controls.js examples: []
The laser-controls component provides a tracked controller with a laser or ray
cursor shooting out to be used for input and interactions. All headsets ship with some form of
tracked input controller that has at least a button to trigger actions. Using laser-controls we can
get a consistent form of interaction that works across all VR platforms with
a single line of HTML.
laser-controls is a higher-order component, meaning the component wraps and
configures other components, rather than implementing any logic itself. Under
the hood, laser-controls sets all of the tracked controller components:
These controller components get activated if its respective controller is
connected and detected via the Gamepad API. Then the model of the actual
controller is used. laser-controls then configures the cursor
component to listen to the appropriate events and passes the hand
property to ensure WebXR events are filtered by controller handedness. This
prevents one controller's trigger press from affecting another controller's
hovered entity. It also configures the raycaster component to draw
the laser.
When the laser intersects with an entity, the length of the line gets truncated
to the distance to the intersection point.
Example
<a-entity laser-controls="hand: left"></a-entity>Properties
| Properties | Description |
|---|---|
| hand | left or right. |
| model | Whether the default model for the controller is loaded. |
| defaultModelColor | Color for the default controller model. |
Customizing the Raycaster
Configure the raycaster properties.
For example:
<a-entity laser-controls raycaster="objects: .links; far: 5"></a-entity>Customizing the Line
See Raycaster: Customizing the Line.
For example:
<a-entity laser-controls raycaster="lineColor: red; lineOpacity: 0.5"></a-entity>Components Layer (`docs/components/layer.md`)
title: layer type: components layout: docs parent_section: components source_code: src/components/layer.js examples: []
The layer component renders images, videos or cubemaps into a WebXR compositor layer on supported browsers.
The benefits of layers are:
- Performance and judder: Once the layer is submitted, the compositor is in charge of rendering it at a fixed refresh rate (72Hz, 90Hz... depending on device). Even if the application cannot keep up with the compositor refresh rate the layer won't drop any frames resulting in a smoother experience.
- Visual fidelity: The images in a layer will be sampled only once by the compositor. Regular WebGL content is sampled twice: Once one rendering to the eye buffer and a second time when copied to the compositor. This results in a loss of details.
- Battery life: No double sampling results in less memory copies reducing operations and battery consumption.
- Latency: Headset pose sampling for layers takes place at the end of the frame using the most recent HMD pose. This paired with reprojection techniques reduce the effective latency.
Example
<a-scene>
<a-assets>
<img id="comicbook" crossOrigin="anonymous" src="/path/to/comicbook.png">
</a-assets>
<a-entity layer="type: quad; src: #comicbook" position="0 1.8 -1.5"></a-entity>
</a-scene>Properties
| Properties | Description |
|---|---|
| type | quad, monocubemap or stereocubemap |
| src | Image or video loaded in the layer. |
| rotateCubemap | Rotates the cubemap 180 degrees in the y-axis. |
Components Light (`docs/components/light.md`)
title: light
type: components
layout: docs
parent_section: components
source_code: src/components/light.js
examples:
- title: Animated Lights
src: https://aframe.io/aframe/examples/showcase/dynamic-lights/
The light component defines the entity as a source of light. Light affects all
materials that have not specified a flat shading model with shader: flat.
Note that lights are computationally expensive hence we should limit number of lights
in a scene.
<a-entity light="color: #AFA; intensity: 1.5" position="-1 1 0"></a-entity>By default, A-Frame scenes inject default lighting, an ambient light and a
directional light. These default lights are visible in the DOM with thedata-aframe-default-light attribute. Whenever we add any lights, A-Frame
removes the default lights from the scene.
<a-entity light="type: ambient; color: #BBB"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.6" position="-0.5 1 1"></a-entity>To manually disable the defaults, without adding other lights:
<a-scene light="defaultLightsEnabled: false">
</a-scene>Properties
All light types support a few basic properties:
| Property | Description | Default Value |
|---|---|---|
| type | One of ambient, directional, hemisphere, point, spot, probe. |
directional |
| color | Light color. | #fff |
| intensity | Light strength. | 1.0 |
Light Types
Different types of lights include unique properties. We will go through each
type, including its properties and when it may be the right choice.
Ambient
Ambient lights globally affect all entities in the scene. The color andintensity properties define ambient lights. Additionally, position,rotation, and scale have no effect on ambient lights.
We recommend to have some form of ambient light such that shadowed areas are
not fully black and to mimic indirect lighting.
<a-entity light="type: ambient; color: #CCC"></a-entity>Directional
Directional lights are like a light source that is infinitely far away, but shining
from a specific direction, like the sun. Thus, absolute position do not have an
effect on the intensity of the light on an entity. We can specify the direction
using the position component.
The example below creates a light source shining from the upper-left at a
45-degree angle. Note that because only the vector matters, position="-100 100 0" and position="-1 1 0" are the same.
<a-entity light="type: directional; color: #EEE; intensity: 0.5" position="-1 1 0"></a-entity>We can specify the direction of the directional light with its orientation by
creating a child entity it targets. For example, pointing down its -Z axis:
<a-light type="directional" position="0 0 0" rotation="-90 0 0" target="#directionaltarget">
<a-entity id="directionaltarget" position="0 0 -1"></a-entity>
</a-light>Directional lights are the most efficient type for adding realtime shadows to a scene. You can use shadows like so:
<a-light type="directional" light="castShadow:true;" position="1 1 1" intensity="0.5" shadow-camera-automatic="#objects"></a-light>The shadow-camera-automatic configuration maps to light.shadowCameraAutomatic which tells the light to automatically update the shadow camera to be the minimum size and position to encompass the target elements.
Hemisphere
Hemisphere lights are like an ambient light, but with two different colors, one
from above (color) and one from below (groundColor). This can be useful for
scenes with two distinct lighting colors (e.g., a grassy field under a gray
sky).
<a-entity light="type: hemisphere; color: #33C; groundColor: #3C3; intensity: 2"></a-entity>| Property | Description | Default Value |
|---|---|---|
| color | Light color from above. | #fff |
| groundColor | Light color from below. | #fff |
Point
Point lights, unlike directional lights, are omni-directional and affect
materials depending on their position and distance. Point lights are like light
bulb. The closer the light bulb gets to an object, the greater the object is
lit.
<a-entity light="type: point; intensity: 0.75; distance: 50; decay: 2"
position="0 10 10"></a-entity>| Property | Description | Default Value |
|---|---|---|
| decay | Amount the light dims along the distance of the light. | 1.0 |
| distance | Distance where intensity becomes 0. If distance is 0, then the point light does not decay with distance. |
0.0 |
Spot
Spot lights are like point lights in the sense that they affect materials
depending on its position and distance, but spot lights are not
omni-directional. They mainly cast light in one direction, like the
Bat-Signal.
<a-entity light="type: spot; angle: 45"></a-entity>| Property | Description | Default Value |
|---|---|---|
| angle | Maximum extent of spot light from its direction (in degrees). | 60 |
| decay | Amount the light dims along the distance of the light. | 1.0 |
| distance | Distance where intensity becomes 0. If distance is 0, then the point light does not decay with distance. |
0.0 |
| penumbra | Percent of the spotlight cone that is attenuated due to penumbra. | 0.0 |
| target | element the spot should point to. set to null to transform spotlight by orientation, pointing to it's -Z axis. | null |
Probe
Probe lights are kind of like ambient lighting in that they don't have a particular source or direction and light everything equally.
Where they differ though is that they will color each angle differently based upon a spherical harmonic. This spherical harmonic is generated by analyzing a cubemap. The cubemap you provide doesn't need to be high resolution since it's only used to generate the spherical harmonics.
| Property | Description | Default Value |
|---|---|---|
| intensity | Amount of light provided | 1 |
| envMap | Cube Map to load | null |
Example:
<a-assets>
<a-cubemap id="pisa">
<img src="https://threejs.org/examples/textures/cube/pisa/px.png">
<img src="https://threejs.org/examples/textures/cube/pisa/nx.png">
<img src="https://threejs.org/examples/textures/cube/pisa/py.png">
<img src="https://threejs.org/examples/textures/cube/pisa/ny.png">
<img src="https://threejs.org/examples/textures/cube/pisa/pz.png">
<img src="https://threejs.org/examples/textures/cube/pisa/nz.png">
</a-cubemap>
</a-assets>
<a-light type="probe" envMap="#pisa"></a-light>Configuring Shadows
A-Frame includes support for realtime shadow rendering. With proper
configuration, objects (both moving or stationary) will cast shadows adding
depth and realism to a scene. Since shadows come with many properties, it
is very helpful to use the A-Frame Inspector to configure shadows
Light types that support shadows (point, spot, and directional) include
additional properties:
| Property | Light type | Description | Default Value |
|---|---|---|---|
| castShadow | Whether this light casts shadows on the scene. | false | |
| shadowBias | Offset depth when deciding whether a surface is in shadow. Tiny adjustments here (in the order of +/-0.0001) may reduce artifacts in shadows. | 0 | |
| shadowCameraAutomatic | directional |
Automatically configure the Bottom, Top, Left, Right and Near of a directional light's shadow map, from an element | |
| shadowCameraBottom | directional |
Bottom plane of shadow camera frustum. | -5 |
| shadowCameraFar | Far plane of shadow camera frustum. | 500 | |
| shadowCameraFov | point, spot |
Shadow camera's FOV. | 50 |
| shadowCameraLeft | directional |
Left plane of shadow camera frustum. | -5 |
| shadowCameraNear | Near plane of shadow camera frustum. | 0.5 | |
| shadowCameraRight | directional |
Right plane of shadow camera frustum. | 5 |
| shadowCameraTop | directional |
Top plane of shadow camera frustum. | 5 |
| shadowCameraVisible | Displays a visual aid showing the shadow camera's position and frustum. This is the light's view of the scene, used to project shadows. | false | |
| shadowMapHeight | Shadow map's vertical resolution. Larger shadow maps display more crisp shadows, at the cost of performance. | 512 | |
| shadowMapWidth | Shadow map's horizontal resolution. | 512 |
Adding Real-Time Shadows
NOTE: Real-time shadows add performance overhead. When objects in a scene are
stationary, or especially when optimizing for mobile devices, be aware of
other techniques for realistic shadows, such as baking light and shadow
information into a texture before importing assets into
A-Frame.
- 1. Create at least one light with
castShadow: true. Three light types
support shadows (point,spot, anddirectional). Of the three,directionallights will have the best performance. Combining an ambient
light (without shadows) and a directional light (with shadows) is a good
place to start.
<a-scene>
<a-entity light="type: ambient; intensity: 0.5;"></a-entity>
<a-entity light="type: directional;
castShadow: true;
intensity: 0.4;
shadowCameraVisible: true;"
position="-5 3 1.5"></a-entity>
</a-scene>In the example above, the directional light has lower intensity than the
ambient light, for slightly softer shadows. Adding shadowCameraVisible: true
creates a visual aid for debugging: objects outside the camera's view cannot
cast or receive shadows.
- 2. Add the shadow component to objects in the scene that should cast or
receive shadows.
<a-gltf-model src="tree.gltf" shadow="cast: true"></a-gltf-model>
<a-circle id="ground" radius="10" rotation="-90 0 0" shadow="receive: true"></a-circle>3. Adjust the shadow camera position and frustum (
shadowCameraTop,shadowCameraRight, ...) of the directional light, until it envelops the
scene tightly. If the frustum is too small, shadows will be missing or
partially clipped. If the frustum is too large, shadows will appear coarse or
pixelated. The size of the shadow map (shadowMapHeight: 512,shadowMapWidth: 512) determines the resolution at which shadows are computed, so tightly
sizing the shadow camera around your scene will make the best use of this
resolution and device performance.4. Refine shadow appearance. Scene-wide options, affecting all lights,
may be configured on the scene's shadow system.
Shadow System Properties
These global options affect the entire scene, and are set using the shadow
system on the <a-scene> root element.
<a-scene shadow="type: pcf">
</a-scene>| Property | Description | Default Value |
|---|---|---|
| type | Defines shadow map type (basic, pcf) with varying appearance and performance characteristics. |
pcf |
Components Line (`docs/components/line.md`)
title: line type: components layout: docs parent_section: components source_code: src/components/line.js examples: []
The line component draws a line given a start coordinate and end coordinate
using THREE.Line.
The raycaster component uses the line component for its showLine
property, which is then used by the laser-controls component.
Example
<a-entity line="start: 0 1 0; end: 2 0 -5; color: red"
line__2="start: -2 4 5; end: 0 4 -3; color: green"></a-entity>Note an entity can have multiple lines. The line mesh is set as line orline__ in the entity's object3DMap:
console.log(el.getObject3D('line'));
console.log(el.getObject3D('line__2'));Properties
| Property | Description | Default Value |
|---|---|---|
| start | Start point coordinate. | 0 0 0 |
| end | End coordinate. | 0 0 0 |
| color | Line color. | #74BEC1 |
| opacity | Line opacity. | 1 |
| visible | Whether the material visible. | true |
Components Link (`docs/components/link.md`)
title: link type: components layout: docs parent_section: components image: src: https://user-images.githubusercontent.com/674727/27721720-19388346-5d17-11e7-912b-499886be0a8d.gif source_code: src/components/link.js examples: []
The link component connects between experiences and allows for traversing between VR web pages. When activated via an event, the link component sends the user to a different page, just like a normal web page redirect. To maintain VR across pages, the following conditions must apply:
- Your browser implements the WebXR in-VR navigation proposal. Notice that is not yet part of the standard. Support is experimental, varies across browsers and it might require enabling manually in settings.
The destination Web page listens to the window [
sessiongranted] event and enters VR. A-Frame experiences behave this way by default.At the moment, The Oculus Browser on Quest is the only browser shipping the in-VR navigation proposal.
Link UX
A link in VR can be anything such as grabbing onto an object, placing something
on our head, clicking something, even eating something! We provide a default
implementation of links as a portal or window, using a 360° image thumbnail
to preview the destination.
The UX of link traversal will continue to refine as we iterate and experiment,
both in content and from the browser.
Controls
The default UX of the link component as a portal can be interacted with using a
cursor or controllers. This is not provided directly in the A-Frame core, but
there is a link-controls component in the A-Frame examples. This component
shows tooltips and provides button mappings for peeking and entering portals.
Example
<a-scene>
<a-assets>
<img id="homeThumbnail" src="home.jpg">
</a-assets>
<a-entity link="href: index.html; title: My Homepage; image: #homeThumbnail"></a-entity>
</a-scene>We also provide a link primitive with a different syntax:
<a-link href="index.html" title="My Homepage" image="#homeThumbnail"></a-link>Properties
| Property | Description | Default Value |
|---|---|---|
| backgroundColor | Inner (background) color of the portal. | red |
| borderColor | Border color of the portal. | white |
| highlighted | Boolean to toggle link highlight color. | false |
| highlightedColor | Border color when highlighted. | '#24CAFF' |
| href | Destination URL where the link points to. | '' |
| image | 360° image used as scene preview in the portal. Can be a selector to an `` element or a URL. | '' |
| on | Event to listen to that triggers link traversal. | 'click' |
| peekMode | Whether the 360° image is fully expanded for preview. | false |
| title | Text displayed on the link. The href or page URL is used if not defined. |
'' |
| titleColor | Color of the text displayed on the link. | white |
| visualAspectEnabled | Whether to enable the default visual appearance of a portal. Set to false if we want to implement our own pattern or form of link traversal. | false |
Manually Navigating
To navigate manually, use window.location API. See this StackOverflow question on navigating. For example:
window.location.href = 'https://supermedium.com/supercraft/';
// or window.location.replaceComponents Loading Screen (`docs/components/loading-screen.md`)
title: loading-screen type: components layout: docs parent_section: components examples: []
The loading screen component configures the loading screen visual style.
To configure the style of the loader title bar one can redefine.a-loader-title style. The example below sets the text color to red:
.a-loader-title {
color: red;
}The title text is set to whatever is in document.title or <title></title>.
Example
The example below sets the background color to black and dots color to red.
<a-scene loading-screen="dotsColor: red; backgroundColor: black"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| dotsColor | Loader dots color. | white |
| backgroundColor | Loader background color. | #24CAFF |
| enabled | Enables / Disables the loading screen. | true |
Components Logitech Mx Ink Controls (`docs/components/logitech-mx-ink-controls.md`)
title: logitech-mx-ink-controls type: components layout: docs parent_section: components source_code: src/components/logitech-mx-ink-controls.js examples: []
The logitech-mx-ink-controls component interfaces with the Logitech MX Ink tracked pen. It
wraps the tracked-controls component while adding button
mappings, events, and a pencil model.
Example
<a-entity logitech-mx-ink-controls="hand: left"></a-entity>
<a-entity logitech-mx-ink-controls="hand: right"></a-entity>Value
| Property | Description | Default Value |
|---|---|---|
| hand | The hand that will be tracked (i.e., right, left). | left |
| model | Whether the Touch controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| tiptouchstart | Tip touched. |
| tiptouchend | Tip no longer touched. |
| tipchanged | Tip changed. |
| frontdown | Front button pressed. |
| frontup | Front button released. |
| frontchanged | Front button changed. |
| reardown | Rear button pressed. |
| rearup | Rear button released. |
| rearchanged | Rear button changed. |
Read tip changes
Listen to the tipchanged event.
<a-entity logitech-mx-ink-controls="hand: left" tip-logging></a-entity>
<a-entity logitech-mx-ink-controls="hand: right" tip-logging></a-entity>AFRAME.registerComponent('tip-logging',{
init: function () {
this.el.addEventListener('tipchanged', this.tipChanged);
},
tipChanged: function (evt) {
console.log("Pen tip changed");
}
});Assets
Components Look Controls (`docs/components/look-controls.md`)
title: look-controls type: components layout: docs parent_section: components source_code: src/components/look-controls.js examples: []
The look-controls component:
- Rotates the entity when we rotate a VR head-mounted display (HMD).
- Rotates the entity when we move the mouse.
- Rotates the entity when we touch-drag the touchscreen.
Example
The look-controls component is usually used alongside the camera
component.
<a-entity camera look-controls position="0 1.6 0"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| enabled | Whether look controls are enabled. | true |
| reverseMouseDrag | Whether to reverse mouse drag. | false |
| reverseTouchDrag | Whether to reverse touch drag. | false |
| touchEnabled | Whether to use touch controls in magic window mode. | true |
| mouseEnabled | Whether to use mouse to move the camera in 2D mode. | true |
| pointerLockEnabled | Whether to hide the cursor using the Pointer Lock API. | false |
| magicWindowTrackingEnabled | Whether gyroscope camera tracking is enabled on mobile devices. | true |
Customizing look-controls
While A-Frame's look-controls component is primarily meant for VR with sensible
defaults to work across platforms, many developers want to use A-Frame for
non-VR use cases (e.g., desktop, touchscreen). We might want to modify the
mouse and touch behaviors.
The best way to configure the behavior is to copy and customize the current
look-controls component code. This allows us to configure the
controls how we want (e.g., limit the pitch on touch, reverse one axis). If we
were to include every possible configuration into the core component, we would
be left maintaining a wide array of flags.
Caveats
If you want to create your own component for look controls, you will have to
copy and paste the HMD-tracking bits into your component. In the future, we may
have a system for people to more easily create their controls.
Components Magicleap Controls (`docs/components/magicleap-controls.md`)
title: magicleap-controls
type: components
layout: docs
parent_section: components
source_code: src/components/magicleap-controls.js
examples: []
The magicleap-controls component interfaces with the Magic Leap controller.
It wraps the tracked-controls component while adding button
mappings, events, and Magic Leap controller model/
Example
<a-entity magicleap-controls></a-entity>
<a-entity magicleap-controls="hand: left"></a-entity>
<a-entity magicleap-controls="hand: right"></a-entity>Value
| Property | Description | Default |
|---|---|---|
| hand | The hand that will be tracked (e.g., right, left). | |
| model | Whether the Magic Leap controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| touchpadchanged | Touchpad changed. |
| touchpaddown | Touchpad pressed. |
| touchpadmoved | Touchpad axis changed. |
| touchpadup | Touchpad released. |
| touchpadtouchstart | Touchpad touched. |
| touchpadtouchend | Touchpad not touched. |
| gripchanged | Grip changed. |
| gripdown | Grip pressed. |
| gripup | Grip released. |
| triggerchanged | Trigger changed. |
| triggerdown | Trigger pressed. |
| triggerup | Trigger released. |
| menuchanged | Menu changed. |
| menudown | Menu pressed. |
| menuup | Menu released. |
Assets
Components Material (`docs/components/material.md`)
title: material
type: components
layout: docs
parent_section: components
source_code: src/components/material.js
examples:
- title: Shader Walkthrough
src: https://codepen.io/machenmusik/pen/WZyQNj - title: Grid Shader
src: https://glitch.com/edit/#!/aframe-simpler-shader?path=public/index.html - title: Real-Time Vertex Displacement Shader
src: https://glitch.com/edit/#!/aframe-displacement-registershader?path=public/index.html - title: Real-Time Vertex Displacement with Offset
src: https://glitch.com/edit/#!/aframe-displacement-offset-registershader?path=public/index.html
The material component gives appearance to an entity. We can define properties
such as color, opacity, or texture. This is often paired with the geometry
component which provides shape.
We can register custom materials to extend the material component to provide a
wide range of visual effects.
Example
Defining a red material using the default standard material:
<a-entity geometry="primitive: box" material="color: red"></a-entity>Here is an example of using a different material:
<a-entity geometry="primitive: box" material="shader: flat; color: red"></a-entity>Here is an example of using an example custom material:
<a-entity geometry="primitive: plane"
material="shader: ocean; color: blue; wave-height: 10"></a-entity>Properties
The material component has some base properties. More properties are available
depending on the material type applied.
| Property | Description | Default Value |
|---|---|---|
| alphaTest | Alpha test threshold for transparency. | 0 |
| depthTest | Whether depth testing is enabled when rendering the material. | true |
| flatShading | Use THREE.FlatShading rather than THREE.StandardShading. |
false |
| offset | Texture offset to be used. | {x: 0, y: 0} |
| opacity | Extent of transparency. If the transparent property is not true, then the material will remain opaque and opacity will only affect color. |
1.0 |
| premultipliedAlpha | Whether the material's RGB channels are premultiplied by its alpha channel. Automatically forced to true when blending is multiply or subtractive. |
false |
| repeat | Texture repeat to be used. | {x: 1, y: 1} |
| magFilter | Which magnifying filter to use when sampling textures. Can be one of linear or nearest. |
linear |
| minFilter | Which minifying filter to use when sampling textures. Can be one of linear, linear-mipmap-nearest, linear-mipmap-linear, nearest, nearest-mipmap-nearest or nearest-mipmap-linear. |
linear-mipmap-linear |
| shader | Which material to use. Defaults to the standard material. Can be set to the flat material or to a registered custom shader material. | standard |
| side | Which sides of the mesh to render. Can be one of front, back, or double. |
front |
| transparent | Whether material is transparent. Transparent entities are rendered after non-transparent entities. | false |
| vertexColorsEnabled | Whether to use vertex colors to shade the material. | false |
| visible | Whether material is visible. Raycasters will ignore invisible materials. | true |
| blending | The blending mode for the material's RGB and Alpha sent to the WebGLRenderer. Can be one of none, normal, additive, subtractive or multiply. |
normal |
| dithering | Whether material is dithered with noise. Removes banding from gradients like ones produced by lighting. | true |
| anisotropy | The anisotropic filtering sample rate to use for the textures. A value of 0 means the default value will be used, see renderer | 0 |
Events
| Event Name | Description |
|---|---|
| materialtextureloaded | Texture loaded onto material. |
| materialvideoloadeddata | Video data loaded and is going to play. |
| materialvideoended | For video textures, emitted when the video has reached its end (may not work with loop). |
Built-in Materials
A-Frame ships with a couple of built-in materials.
`standard`
The standard material is the default material. It uses the physically-based
THREE.MeshStandardMaterial.
Properties
These properties are available on top of the base material properties.
| Property | Description | Default Value |
|---|---|---|
| ambientOcclusionMap | Ambient occlusion map. Used to add shadows to the mesh. Can either be a selector to an ``, or an inline URL. Requires 2nd set of UVs (see below). | None |
| ambientOcclusionMapIntensity | The intensity of the ambient occlusion map, a number between 0 and 1. | 1 |
| ambientOcclusionTextureRepeat | How many times the ambient occlusion texture repeats in the X and Y direction. | 1 1 |
| ambientOcclusionTextureOffset | How the ambient occlusion texture is offset in the x y direction. | 0 0 |
| color | Base diffuse color. | #fff |
| displacementMap | Displacement map. Used to distort a mesh. Can either be a selector to an ``, or an inline URL. | None |
| displacementScale | The intensity of the displacement map effect | 1 |
| displacementBias | The zero point of the displacement map. | 0.5 |
| displacementTextureRepeat | How many times the displacement texture repeats in the X and Y direction. | 1 1 |
| displacementTextureOffset | How the displacement texture is offset in the x y direction. | 0 0 |
| emissive | The color of the emissive lighting component. Used to make objects produce light even without other lighting in the scene. | #000 |
| emissiveIntensity | Intensity of the emissive lighting component. | 1 |
| height | Height of video (in pixels), if defining a video texture. | 360 |
| envMap | Environment cubemap texture for reflections. Can be a selector to |
None |
| fog | Whether or not material is affected by fog. | true |
| metalness | How metallic the material is from 0 to 1. |
0 |
| normalMap | Normal map. Used to add the illusion of complex detail. Can either be a selector to an ``, or an inline URL. | None |
| normalScale | Scale of the effect of the normal map in the X and Y directions. | 1 1 |
| normalTextureRepeat | How many times the normal texture repeats in the X and Y direction. | 1 1 |
| normalTextureOffset | How the normal texture is offset in the x y direction. | 0 0 |
| repeat | How many times a texture (defined by src) repeats in the X and Y direction. |
1 1 |
| roughness | How rough the material is from 0 to 1. A rougher material will scatter reflected light in more directions than a smooth material. |
0.5 |
| sphericalEnvMap | Environment spherical texture for reflections. Can either be a selector to an ``, or an inline URL. | None |
| width | Width of video (in pixels), if defining a video texture. | 640 |
| wireframe | Whether to render just the geometry edges. | false |
| wireframeLinewidth | Width in px of the rendered line. | 2 |
| src | Image or video texture map. Can either be a selector to an `` or <video>, or an inline URL. |
None |
Physically-Based Shading
Physically-based shading is a shading model that aims to make materials behave
realistically to lighting conditions. Appearance is a result of the interaction
between the incoming light and the properties of the material.
To achieve realism, the diffuse color, metalness, roughness properties of
the material must be accurately controlled, often based on real-world material
studies. Some people have compiled charts of realistic values for different
kinds of materials that we can use as a starting point.
For example, for a tree bark material, as an estimation, we might set:
<a-entity geometry="primitive: cylinder"
material="src: treebark.png; color: #696969; roughness: 1; metalness: 0">
</a-entity>Phong-Based Shading
Phong shading is an inexpensive shader model which whilst less realistic than the
standard material is better than flat shading.
To use it set the shader to phong in the material:
<a-torus-knot position="0 3 0" material="shader:phong; reflectivity: 0.9; shininess: 30;"
geometry="radius: 0.45; radiusTubular: 0.09">
</a-torus-knot>It has the following properties you can use:
| Name | Description | Default |
|---|---|---|
| specular | This defines how shiny the material is and the color of its shine. | #111111 |
| shininess | How shiny the specular highlight is; a higher value gives a sharper highlight | 30 |
| transparent | Whether the material is transparent | false |
| combine | How the environment map mixes with the material. "mix", "add" or "multiply" | "mix" |
| reflectivity | How much the environment map affects the surface | 0.9 |
| refract | Whether the defined envMap should refract | false |
| refractionRatio | 1/refractive index of the material | 0.98 |
Distortion Maps
There are three properties which give the illusion of complex geometry:
- Ambient occlusion maps - Applies subtle shadows in areas that receive less
ambient light. Direct (point, directional) lights do not affect ambient occlusion
maps. Baked ambient occlusion requires a 2nd set of UVs, which may be added to
the mesh in modeling software or using JavaScript. - Displacement maps - Distorts a simpler model at a high resolution
allowing more detail. This will affect the mesh's silhouette but can be
expensive. - Normal maps - Defines the angle of the surface at that point. Giving the
appearance of complex geometry without distorting the model. This does not
change the geometry but normal maps are cheaper.
Environment Maps
The envMap and sphericalEnvMap properties define what environment
the material reflects. The clarity of the environment reflection depends
on the metalness, and roughness properties.
The sphericalEnvMap property takes a single spherical mapped
texture. Of the kind you would assign to a <a-sky>.
Unlike textures, the envMap property takes a cubemap, six images put together
to form a cube. The cubemap wraps around the mesh and applied as a texture.
For example:
<a-scene>
<a-assets>
<a-cubemap id="sky">
<img src="right.png">
<img src="left.png">
<img src="top.png">
<img src="bottom.png">
<img src="front.png">
<img src="back.png">
</a-cubemap>
</a-assets>
<a-entity geometry="primitive: box" material="envMap: #sky; roughness: 0"></a-entity>
</a-scene>Alternatively, you can include the URLs for the cubemap images directly in the material component like this:
<a-entity geometry="primitive: box"
material="envMap: url(right.png),
url(left.png),
url(top.png),
url(bottom.png),
url(front.png),
url(back.png);
roughness: 0">
</a-entity>`flat`
The flat material uses the THREE.MeshBasicMaterial. Flat materials
are not affected by the scene's lighting conditions. This is useful for things
such as images or videos. Set shader to flat:
<a-entity geometry="primitive: plane" material="shader: flat; src: #cat-image"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| color | Base diffuse color. | #fff |
| fog | Whether or not material is affected by fog. | true |
| height | Height of video (in pixels), if defining a video texture. | 360 |
| repeat | How many times a texture (defined by src) repeats in the X and Y direction. |
1 1 |
| src | Image or video texture map. Can either be a selector to an `` or <video>, or an inline URL. |
None |
| toneMapped | Whether to ignore toneMapping, set to false you are using renderer.toneMapping and an element should appear to emit light. | true |
| width | Width of video (in pixels), if defining a video texture. | 640 |
| wireframe | Whether to render just the geometry edges. | false |
| wireframeLinewidth | Width in px of the rendered line. | 2 |
Textures
To set a texture using one of the built-in materials, specify the src
property. src can be a selector to either an `` or <video> element in the
asset management system:
<a-scene>
<a-assets>
<img id="my-texture" src="texture.png">
</a-assets>
<a-entity geometry="primitive: box" material="src: #my-texture"></a-entity>
</a-scene>src can also be an inline URL. Note that we do not get browser caching or
preloading through this method.
<a-scene>
<a-entity geometry="primitive: box" material="src: url(texture.png)"></a-entity>
</a-scene>Most of the other properties works together with textures. For example, thecolor property will act as the base color and multiplies per pixel with the
texture. Set it to #fff to maintain the original colors of the texture.
A-Frame caches textures so as to not push redundant textures to the GPU.
Video Textures
Whether the video texture loops or autoplays depends on the video element used
to create the texture. If we simply pass a URL instead of creating and passing
a video element, then the texture will loop and autoplay by default. To specify
otherwise, create a video element in the asset management system, and pass a
selector for the id attribute (e.g., #my-video):
Video autoplay policies are getting more and more strict and rules might vary across browsers. Mandatory user gesture is now commonly enforced. For maximum compatibility, you can offer a button that the user can click to start video playback. Simple sample code can be found in the docs. Pay particular attention to the play-on-click component
<a-scene>
<a-assets>
<video id="my-video" src="video.mp4" autoplay="true"></video>
</a-assets>
<a-entity geometry="primitive: box" material="src: #my-video"></a-entity>
</a-scene>Controlling Video Textures
To control the video playback such as pausing or seeking, we can use the video
element to control media playback. For example:
var videoEl = document.querySelector('#my-video');
videoEl.currentTime = 122; // Seek to 122 seconds.
videoEl.pause();This doesn't work as well if you are passing an inline URL, in which case
A-Frame creates a video element internally. To get a handle on the video
element, we should define one in <a-assets>.
Canvas Textures
We can use a <canvas> as a texture source. If the canvas if modified, you'll need to refresh the texture by using code that follows the example shown here.
<script>
AFRAME.registerComponent('draw-canvas', {
schema: {default: ''},
init: function () {
this.canvas = document.getElementById(this.data);
this.ctx = this.canvas.getContext('2d');
// Draw on canvas...
}
});
</script>
<a-assets>
<canvas id="my-canvas" crossorigin="anonymous"></canvas>
</a-assets>
<a-entity geometry="primitive: plane" material="src: #my-canvas"
draw-canvas="my-canvas"></a-entity>Repeating Textures
We might want to repeat tile textures rather than having them stretch. The repeat
property can repeat textures.
<a-entity geometry="primitive: plane; width: 100"
material="src: carpet.png; repeat: 100 20"></a-entity>Transparency Issues
Transparency and alpha channels are tricky in 3D graphics. If you are having
issues where transparent materials in the foreground do not composite correctly
over materials in the background, the issues are probably due to underlying
design of the OpenGL compositor (which WebGL is an API for).
In an ideal scenario, transparency in A-Frame would "just work", regardless of
where the developer places an entity in 3D space, or in which order they define
the elements in markup. We can often run into scenarios where foreground
entities occlude background entities. This creates confusion and unwanted
visual defects.
To work around this issue, try changing the order of the entities in the HTML.
When using PNG images as cutouts or masks (where part of the image should be
fully transparent, and the rest fully opaque), try setting transparent: false
and like alphaTest: 0.5 to solve transparency issues. Play around with the alpha
test value.
`render-order` Component
Use the render-order component to tell the render to sort transparent objects
by depth and to be able to manually define render order of entities in HTML via
named layers. If you have transparency ordering issues, use this component.
Register a Custom Shader Material
We can register custom shader materials for appearances and effects usingAFRAME.registerShader.
Let's walk through an example CodePen with step-by-step commentary.
As always, we need to include the A-Frame script.
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>Next, we define any components and shaders we need after the A-Frame
script but before the scene declaration. Here, we begin our my-custom shader.
The schema declares any parameters for the shader.
<script>
AFRAME.registerShader('my-custom', {
schema: {
// ...
}
});
</script>We usually want to support the color and opacity properties. is: 'uniform' tells A-Frame this property should appear as uniform value in the
shaders:
<script>
AFRAME.registerShader('my-custom', {
schema: {
color: {type: 'color', is: 'uniform', default: 'red'},
opacity: {type: 'number', is: 'uniform', default: 1.0}
}
});
</script>Setting raw to true uses THREE.RawShaderMaterial instead of
ShaderMaterial so built-in uniforms and attributes are not
automatically added to your shader code. Here we want to include the usual
prefixes with GLSL constants and such, so leave it false.
raw: false,We're going to use the default vertex shader by omitting vertexShader. Note
that if our fragment shader cares about texture coordinates, our vertex shader
should set varying values to use in the fragment shader.
Since almost every WebVR-capable browser supports ES6, we define our fragment
shader as a multi-line string:
fragmentShader:
`
// Use medium precision.
precision mediump float;
// This receives the color value from the schema, which becomes a vec3 in the shader.
uniform vec3 color;
// This receives the opacity value from the schema, which becomes a number.
uniform float opacity;
// This is the shader program.
// A fragment shader can set the color via gl_FragColor,
// or decline to draw anything via discard.
void main () {
// Note that this shader doesn't use texture coordinates.
// Set the RGB portion to our color,
// and the alpha portion to our opacity.
gl_FragColor = vec4(color, opacity);
}
`
});
</script>And using our shader from the material component:
<a-box material="shader: my-custom; color: blue; opacity: 0.7; transparent: true" position="0 0 -2"></a-box>`registerShader`
Like components, custom materials have schema and lifecycle handlers.
| Property | Description |
|---|---|
| fragmentShader | Optional string containing the fragment shader. If omitted, a simple default is used. |
| init | Optional lifecycle handler called once during shader initialization. Used to create the material. |
| raw | Optional. If true, uses THREE.RawShaderMaterial to accept shaders verbatim. If false (default), uses THREE.ShaderMaterial. |
| schema | Defines properties, uniforms, attributes that the shader will use to extend the material component. |
| update | Optional lifecycle handler called once during shader initialization and when data is updated. Used to update the material or shader. |
| vertexShader | Optional string containing the vertex shader. If omitted, a simple default is used. |
Schema
We can define material properties just as we would with component properties.
The data will act as the data we use to create our material:
AFRAME.registerShader('custom', {
schema: {
emissive: {default: '#000'},
wireframe: {default: false}
}
});To pass data values into the shader(s) as uniform values, include is: 'uniform' in the definition:
AFRAME.registerShader('my-custom', {
schema: {
color: {type:'color', is:'uniform', default:'red'},
opacity: {type:'number', is:'uniform', default:1.0}
},
...Supported Uniform Types
The uniform types supported by A-Frame are summarized in the table below. Note
that time can eliminate the need for a tick() handler in many cases.
| A-Frame Type | THREE Type | GLSL Shader Type |
|---|---|---|
| array | v3 | vec3 |
| color | v3 | vec3 |
| int | i | int |
| number | f | float |
| map | t | map |
| time | f | float (milliseconds) |
| vec2 | v2 | vec2 |
| vec3 | v3 | vec3 |
| vec4 | v4 | vec4 |
Example - GLSL and Shaders
For more customized visual effects, we can write GLSL shaders and apply them to
A-Frame entities.
NOTE: GLSL, the syntax used to write shaders, may seem a bit scary at first.
For a gentle (and free!) introduction, we recommend The Book of
Shaders.
Here are the vertex and fragment shaders we'll use:
// vertex.glsl
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}// fragment.glsl
varying vec2 vUv;
uniform vec3 color;
uniform float timeMsec; // A-Frame time in milliseconds.
void main() {
float time = timeMsec / 1000.0; // Convert from A-Frame milliseconds to typical time in seconds.
// Use sin(time), which curves between 0 and 1 over time,
// to determine the mix of two colors:
// (a) Dynamic color where 'R' and 'B' channels come
// from a modulus of the UV coordinates.
// (b) Base color.
//
// The color itself is a vec4 containing RGBA values 0-1.
gl_FragColor = mix(
vec4(mod(vUv , 0.05) * 20.0, 1.0, 1.0),
vec4(color, 1.0),
sin(time)
);
}To use these vertex and fragment shaders, after reading them into stringsvertexShader and fragmentShader, we register our custom shader with
A-Frame:
// shader-grid-glitch.js
AFRAME.registerShader('grid-glitch', {
schema: {
color: {type: 'color', is: 'uniform'},
timeMsec: {type: 'time', is: 'uniform'}
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});And using from HTML markup:
<a-sphere material="shader:grid-glitch; color: blue;" radius="0.5" position="0 1.5 -2"></a-sphere>For an example with textures, Remix this Texture Shader on Glitch
For a more advanced example, try Real-Time Vertex Displacement.
Using a Custom Shader and Component Together
Let's take the real-time vertex displacement shader example above, and add the
capability to apply an offset based upon the camera's position. We declare
that offset as a uniform vec3 value myOffset:
AFRAME.registerShader('displacement-offset', {
schema: {
timeMsec: {type: 'time', is: 'uniform'},
myOffset: {type: 'vec3', is: 'uniform'}
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});Used by this vertex shader. So how do we updatemyOffset to be the camera position from A-Frame such that the vertex shader
behaves correctly? The typical method to do this in A-Frame is to create a
component with the desired functionality, and attach it to the appropriate
entity.
Note that the shader property is exposed via the material component, so we
modify the single property of interest using a form of setAttribute(). As
best practice to avoid creating garbage for performance reasons:
- Do not use the form of
setAttributethat takes an object as second argument. - Create a component property to hold the offset, to avoid creating a new
THREE.Vector3every tick.
AFRAME.registerComponent('myoffset-updater', {
init: function () {
this.offset = new THREE.Vector3();
},
tick: function (t, dt) {
this.offset.copy(this.el.sceneEl.camera.el.getAttribute('position'));
this.offset.y = 0;
this.el.setAttribute('material', 'myOffset', this.offset);
}
});We then apply the component to the entity with the custom shader:
<a-scene>
<a-sphere
animation="property: scale; dir: alternate; dur: 5000; loop: true; to: 4 4 4"
geometry="radius: 0.2"
material="shader: displacement-offset"
myoffset-updater
position="0 1.5 -2">
</a-sphere>
<a-box color="#CCC" width="3" depth="3" height="0.1" position="0 0 -2"></a-box>
</a-scene>Voila!
Another good example of using a component to set shader values is the A-Frame
Shaders example. This
component reacts to rotation updates to the element with id orbit by
computing the sunPosition vector to use in the sky shader:
AFRAME.registerComponent('sun-position-setter', {
init: function () {
var skyEl = this.el;
var orbitEl = this.el.sceneEl.querySelector('#orbit');
orbitEl.addEventListener('componentchanged', function changeSun (evt) {
var sunPosition;
var phi;
var theta;
if (evt.detail.name !== 'rotation') { return; }
sunPosition = orbitEl.getAttribute('rotation');
if(sunPosition === null) { return; }
theta = Math.PI * (- 0.5);
phi = 2 * Math.PI * (sunPosition.y / 360 - 0.5);
skyEl.setAttribute('material', 'sunPosition', {
x: Math.cos(phi),
y: Math.sin(phi) * Math.sin(theta),
z: -1
});
});
}
});In addition, there are components developed by the A-Frame developer community
that allow the use of existing shaders from repositories such as
ShaderToy and ShaderFrog.
Note however that these shaders can be quite demanding in terms of
computational and graphics power, and some more complex shaders may not
function well on lower-performance devices such as smartphones.
Creating a Material from a Component
For those cases where the registerShader API lacks needed functionality
(e.g., no tick handler, some missing uniform types), we recommend creating a
custom material by creating three.js materials (e.g., RawShaderMaterial,ShaderMaterial) within a component:
AFRAME.registerComponent('custom-material', {
schema: {
// Add properties.
},
init: function () {
this.el.addEventListener("loaded", e => { // when using gltf models use "model-loaded" instead
this.material = this.el.getObject3D('mesh').material = new THREE.ShaderMaterial({
// ...
});
});
},
update: function () {
// Update `this.material`.
}
});Components Meta Touch Controls (`docs/components/meta-touch-controls.md`)
title: meta-touch-controls type: components layout: docs parent_section: components source_code: src/components/meta-touch-controls.js examples: []
The meta-touch-controls (formerly oculus-touch-controls) component interfaces with the Meta Touch controllers (Rift, Rift S, Oculus Quest 1, 2, 3 and 3s). It
wraps the tracked-controls component while adding button
mappings, events, and a Touch controller model.
Example
<a-entity meta-touch-controls="hand: left"></a-entity>
<a-entity meta-touch-controls="hand: right"></a-entity>Value
| Property | Description | Default Value |
|---|---|---|
| hand | The hand that will be tracked (i.e., right, left). | left |
| model | Whether the Touch controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| triggerdown | Trigger pressed. |
| triggerup | Trigger released. |
| triggertouchstart | Trigger touched. |
| triggertouchend | Trigger no longer touched. |
| triggerchanged | Trigger changed. |
| thumbstickdown | Thumbstick pressed. |
| thumbstickup | Thumbstick released. |
| thumbsticktouchstart | Thumbstick touched. |
| thumbsticktouchend | Thumbstick no longer touched. |
| thumbstickchanged | Thumbstick changed. |
| thumbstickmoved | Thumbstick direction changed. |
| gripdown | Grip button pressed. |
| gripup | Grip button released. |
| griptouchstart | Grip button touched. |
| griptouchend | Grip button no longer touched. |
| gripchanged | Grip button changed. |
| abuttondown | A button pressed. |
| abuttonup | A button released. |
| abuttontouchstart | A button touched. |
| abuttontouchend | A button no longer touched. |
| abuttonchanged | A button changed. |
| bbuttondown | B button pressed. |
| bbuttonup | B button released. |
| bbuttontouchstart | B button touched. |
| bbuttontouchend | B button no longer touched. |
| bbuttonchanged | B button changed. |
| xbuttondown | X button pressed. |
| xbuttonup | X button released. |
| xbuttontouchstart | X button touched. |
| xbuttontouchend | X button no longer touched. |
| xbuttonchanged | X button changed. |
| ybuttondown | Y button pressed. |
| ybuttonup | Y button released. |
| ybuttontouchstart | Y button touched. |
| ybuttontouchend | Y button no longer touched. |
| ybuttonchanged | Y button changed. |
| surfacedown | Surface button pressed. |
| surfaceup | Surface button released. |
| surfacetouchstart | Surface button touched. |
| surfacetouchend | Surface button no longer touched. |
| surfacechanged | Surface button changed. |
Read thumbstick values
Listen to the thumbstick event and the values are available in the object passed to the handler
<a-entity meta-touch-controls="hand: left" thumbstick-logging></a-entity>
<a-entity meta-touch-controls="hand: right" thumbstick-logging></a-entity>AFRAME.registerComponent('thumbstick-logging',{
init: function () {
this.el.addEventListener('thumbstickmoved', this.logThumbstick);
},
logThumbstick: function (evt) {
if (evt.detail.y > 0.95) { console.log("DOWN"); }
if (evt.detail.y < -0.95) { console.log("UP"); }
if (evt.detail.x < -0.95) { console.log("LEFT"); }
if (evt.detail.x > 0.95) { console.log("RIGHT"); }
}
});Assets
Components Obb Collider (`docs/components/obb-collider.md`)
title: obb-collider type: components layout: docs parent_section: components source_code: src/components/obb-collider.js examples: []
Collision system using Oriented Bounding Boxes based on THREE.OBB.
It checks collision across all entities with the obb-collider component. It emits events when bounding boxes start and stop intersecting.
Example
<a-entity obb-collider></a-entity>Set showColliders to true on the scene to render colliders for debugging purposes:
<a-scene obb-collider="showColliders: true"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| size | Force collider to a specific size | 0 0 0 |
| trackedObject3D | Variable path to the object3D used to calculate the collider. el.object3D by default | '' |
| minimumColliderDimension | Minimum dimension size for the collider. Not practical if too small. e.g planes or small models | 0.02 |
| centerModel | Centers the model before calculating the bounding box and corresponding collider. | false |
Events
| Event Name | Description |
|---|---|
| obbcollisionstarted | Emitted on the entities that start colliding. Event detail will contain withEl referencing the entity that has collided with. |
| obbcollisionended | Emitted on the entities that stop colliding. Event detail will contain withEl referencing the entity that was colliding with |
Components Obj Model (`docs/components/obj-model.md`)
title: obj-model
type: components
layout: docs
parent_section: components
source_code: src/components/obj-model.js
examples:
- title: Modifying Material of Model
src: https://aframe.io/aframe/examples/test/gltf-model/modify-materials.html
The obj-model component loads a 3D model and material using a
Wavefront (.OBJ) file and a .MTL file.
Example
We can load an .OBJ model by pointing to assets that specify the path to an .OBJ and .MTL file.
<a-scene>
<a-assets>
<a-asset-item id="tree-obj" src="/path/to/tree.obj"></a-asset-item>
<a-asset-item id="tree-mtl" src="/path/to/tree.mtl"></a-asset-item>
</a-assets>
<a-entity obj-model="obj: #tree-obj; mtl: #tree-mtl"></a-entity>
</a-scene>Properties
| Property | Description |
|---|---|
| obj | Selector to an <a-asset-item> pointing to a .OBJ file or an inline path to a .OBJ file. |
| mtl | Selector to an <a-asset-item> pointing to a .MTL file or an inline path to a .MTL file. Optional if you wish to use the material component instead. |
Events
| Event Name | Description |
|---|---|
| model-loaded | .OBJ model has been loaded into the scene. |
Loading Inline
We can also load assets by specifying the path directly within url(). Note this is less performant than going through the asset management system.
<a-entity obj-model="obj: url(/path/to/tree.obj); mtl: url(/path/to/tree.mtl)"></a-entity>Troubleshooting
See Introduction → 3D Models → Troubleshooting.
Additional Resources
We can find and download models on the web to drop into our scenes:
- Sketchup's 3D Warehouse - Repository of models.
- Clara.io - Repository of models.
- Blender - A free open-source 3D modeling program with plenty of existing learning resources to create models.
Components Pool (`docs/components/pool.md`)
title: pool type: components layout: docs parent_section: components source_code: src/components/scene/pool.js examples: []
The pool component allows for object
pooling. This gives us a
reusable pool of entities to avoid creating and destroying the same kind of
entities in dynamic scenes. Object pooling helps reduce garbage collection pauses.
Note that entities requested from the pool are paused by default and you need
to call .play() in order to activate their components' tick functions.
For performance reasons, unused entities in the pool are detached from the THREE.js scene graph, which means that they are not rendered, their matrices are not updated, and they are excluded from raycasting.
Example
For example, we may have a game with enemy entities that we want to reuse.
<a-scene pool__enemy="mixin: enemy; size: 10"></a-scene>var el = sceneEl.components.pool__enemy.requestEntity();
el.play();
sceneEl.components.pool__enemy.returnEntity(el);Properties
| Property | Description | Default Value |
|---|---|---|
| container | Selector to store pooled entities. Defaults to the scene. | '' |
| dynamic | Grow the pool automatically if more entities are requested after reaching the size. |
false |
| mixin | Mixin required to initialize the entities of the pool. | '' |
| size | Number of preallocated entities in the pool. | 0 |
Methods
.requestEntity ()
Request one of the available entities in the pool. Will return undefined and
log a warning if dynamic is set to false and you have exhausted the pool.
.returnEntity (entityEl)
Relinquish an entity back to the pool. Will log a warning if you attempt to return
an entity that did not belong to this pool.
Components Position (`docs/components/position.md`)
title: position type: components layout: docs parent_section: components source_code: src/components/position.js examples: []
The position component places entities at certain spots in 3D space. Position
takes a coordinate value as three space-delimited numbers.
All entities inherently have the position component.
Example
<a-entity position="0 1 -1"></a-entity>Value
A-Frame uses a right-handed coordinate system where the negative Z axis extends into the screen. The table below assumes looking down the negative Z axis from the origin.
| Axis | Description | Default Value |
|---|---|---|
| x | Negative X axis extends left. Positive X Axis extends right. | 0 |
| y | Negative Y axis extends down. Positive Y Axis extends up. | 0 |
| z | Negative Z axis extends in. Positive Z Axis extends out. | 0 |
Relative Positioning
World-space positions of child entities inherit from parent entities. Consider this scene:
<a-entity id="parent" position="1 2 3">
<a-entity id="child1"></a-entity>
<a-entity id="child2" position="2 3 4"></a-entity>
</a-entity>The world-space position of #child1 would be 1 2 3 as inherited by the
entity. In the local parent's space, #child1's position would be 0 0 0.
The world-space position of #child2 would be 3 5 7, by combining the
position with the parent entity. In the parent's local space, #child2's
position would be 2 3 4.
Updating Position
For performance and ergonomics, we recommend updating position directly via the
three.js Object3D .position Vector3 versus via.setAttribute.
This method is easier because we have access to all the Vector3
utilities, and faster by skipping .setAttribute overhead and not
needing to create an object to set position:
// With three.js
el.object3D.position.set(1, 2, 3);
// With .setAttribute (less recommended).
el.setAttribute('position', {x: 1, y: 2, z: 3});We can also do incremental updates (which is just modifying a number) and use
Vector3 utilities:
el.object3D.position.x += 1;
el.object3D.position.multiplyScalar(2);
el.object3D.position.sub(someOtherVector);Getting Position
To reflect updates done at the three.js level, A-Frame returns the actualObject3D.position vector object when doing .getAttribute('position'). Note
modifying the return value will modify the entity itself.
See also reading position and rotation of the camera.
Order of Transformations
Transformations are applied to entities in this order:
Components Raycaster (`docs/components/raycaster.md`)
title: raycaster type: components layout: docs parent_section: components source_code: src/components/raycaster.js examples: []
The raycaster component provides line-based intersection testing with a
raycaster. Raycasting is the method of extending a line from
an origin towards a direction, and checking whether that line intersects with
other entities.
The raycaster component uses the three.js raycaster. The raycaster
checks for intersections at a certain interval against a list of objects, and
will emit events on the entity when it detects intersections or clearing of
intersections (i.e., when the raycaster is no longer intersecting an entity).
We prescribe that the set of objects that the raycaster tests for intersection
is explicitly defined via the objects selector property described below.
Raycasting is an expensive operation, and we should raycast against only
targets that need to be interactable at any given time.
The cursor component and laser-controls components
both build on top of the raycaster component.
Example
<a-entity id="player" >
<a-entity collider-check raycaster="objects: .collidable; showLine:true;" position="0 1 0"></a-entity>
</a-entity>
<a-entity class="collidable" geometry="primitive: box" position="0 1 -3"></a-entity>Whenever an entity adds or removes the class collidable, the raycaster will
refresh its list of objects it is raycasting against.
AFRAME.registerComponent('collider-check', {
dependencies: ['raycaster'],
init: function () {
this.el.addEventListener('raycaster-intersection', function () {
console.log('Player hit something!');
});
}
});Properties
| Property | Description | Default Value |
|---|---|---|
| autoRefresh | Whether to automatically refresh raycaster's list of objects to test for intersection using mutation observers to detect added or removed entities and components. | true |
| direction | Vector3 coordinate of which direction the ray should point from relative to the entity's origin. | 0, 0, -1 |
| enabled | Whether raycaster is actively checking for intersections. | true |
| far | Maximum distance under which resulting entities are returned. Cannot be lower than near. |
1000 |
| interval | Number of milliseconds to wait in between each intersection test. Lower number is better for faster updates. Higher number is better for performance. Intersection tests are performed at most once per frame. | 0 |
| lineColor | Raycaster line color if showLine is enabled. | white |
| lineOpacity | Raycaster line opacity if showLine is enabled. | 1 |
| near | Minimum distance over which resulting entities are returned. Cannot be lower than 0. | 0 |
| objects | Query selector to pick which objects to test for intersection. If not specified, all entities will be tested. Note that only objects attached via .setObject3D and their recursive children will be tested. |
null |
| origin | Vector3 coordinate of where the ray should originate from relative to the entity's origin. | 0, 0, 0 |
| showLine | Whether or not to display the raycaster visually with the line component. | false |
| useWorldCoordinates | Whether the raycaster origin and direction properties are specified in world coordinates. | false |
Events
The raycaster component is useful because of the events it emits on entities. It will emit events on both the raycasting entity and the intersected entities.
| Event Name | Description |
|---|---|
| raycaster-intersected | Emitted on the intersected entity. Entity is intersecting with a raycaster. Event detail will contain el, the raycasting entity, and intersection, and .getIntersection (el) function which can be used to obtain current intersection data. |
| raycaster-intersected-cleared | Emitted on the intersected entity. Entity is no longer intersecting with a raycaster. Event detail will contain el, the raycasting entity. |
| raycaster-intersection | Emitted on the raycasting entity. Raycaster is intersecting with one or more entities. Event detail will contain els, an array with the newly intersected entities, and intersections, and .getIntersection (el) function which can be used to obtain current intersection data. For access to a complete list of intersections (existing & new), see Members intersectedEls. |
| raycaster-intersection-cleared | Emitted on the raycasting entity. Raycaster is no longer intersecting with one or more entities. Event detail will contain clearedEls, an array with the formerly intersected entities. |
| raycaster-closest-entity-changed | The closest intersected entity has changed |
Intersection Object
The event detail contains intersection objects. They are returned straight from
three.js Raycaster.intersectObjects.:
| Property | Description |
|---|---|
| distance | distance between the origin of the ray and the intersection |
| point | point of intersection, in world coordinates |
| face | intersected face |
| faceIndex | index of the intersected face |
| indices | indices of vertices comprising the intersected face |
| object | the intersected object |
| uv | U,V coordinates at point of intersection |
Members
| Member | Description |
|---|---|
| intersectedEls | Entities currently intersecting the raycaster. |
| objects | three.js objects to test for intersections. Will be scene.children if objects property is not specified. |
| raycaster | three.js raycaster object. |
Methods
| Method | Description |
|---|---|
| getIntersection (el) | Given an entity, return current intersection data if any. This method is also passed into intersection event details for convenience. |
| refreshObjects | Refreshes the list of objects based off of the objects property to test for intersection. |
Selecting Entities to Test for Intersection
Raycasting is a relatively expensive operation. We heavily recommend and
prescribe setting the objects property which will filter what entities the
raycaster is listening to for intersections. Selective intersections are good
for performance to limit the number of entities to test for intersection since
intersection testing is an operation that many times per second.
To select or pick the entities we want to test for intersection, we can use theobjects property. If this property is not defined, then the raycaster will
test every object in the scene for intersection. objects takes a query
selector value:
<a-entity raycaster="objects: .clickable" cursor></a-entity>
<a-entity class="clickable" geometry="primitive: box" position="1 0 0"></a-entity>
<a-entity class="not-clickable" geometry="primitive: sphere" position="-1 0 0"></a-entity>In that example, we can remove or add entities to the raycast list by setting
or removing the clickable class (el.classList.toggle('clickable')). Another
good way to filter is using data attributes instead of classes
([data-raycastable] and el.setAttribute('data-raycastable', '')).
Listening for Raycaster Intersection Data Change
When we want to listen for change to the intersection data (e.g., listen to
change of the actual point of intersection), we can use the .getIntersection (el) method, which takes an entity and returns intersection data if the
raycaster is currently intersecting the entity. Below is an example component
of doing so in the tick handler:
AFRAME.registerComponent('raycaster-listen', {
init: function () {
// Use events to figure out what raycaster is listening so we don't have to
// hardcode the raycaster.
this.el.addEventListener('raycaster-intersected', evt => {
this.raycaster = evt.detail.el;
});
this.el.addEventListener('raycaster-intersected-cleared', evt => {
this.raycaster = null;
});
},
tick: function () {
if (!this.raycaster) { return; } // Not intersecting.
let intersection = this.raycaster.components.raycaster.getIntersection(this.el);
if (!intersection) { return; }
console.log(intersection.point);
}
});
// <a-entity id="raycaster" raycaster></a-entity>
// <a-entity geometry material raycaster-listen></a-entity>Now on every frame, the entity will check its intersection data and do
something with it (e.g., draw a sphere at the point of intersection).
Manually Refreshing the Target Entities of the Raycaster
The raycaster component keeps a local array of objects and entities that the
raycaster tests against for intersection. This array defaults to every 3D
object in the three.js Scene. If the objects property is specified, then
building this array requires running query selectors and additional filtering.
By default with autoRefresh set to true, the raycaster component will
automatically refresh this list when it detects entities or components are
added and removed. While it is more friendly to auto-refresh, more advanced
developers may want to disable autoRefresh and control when the raycaster
is refreshed for performance.
To manually refresh the list of objects that the raycaster component tests
against, call the .refreshObjects() method:
var raycasterEl = AFRAME.scenes[0].querySelector('[raycaster]');
raycasterEl.components.raycaster.refreshObjects();A-Frame will call .refreshObjects() automatically when an entity is appended
or detached from the scene, but it will not get called during normal DOM
mutations (e.g., some entity changes its class).
Customizing the Line
If showLine is set to true, the raycaster will configure the line given the
raycaster's origin, direction, and far properties. To customize the line
appearance provided by the showLine: true property, we can use the lineColor and lineOpacity:
<a-entity raycaster="showLine: true; far: 100; lineColor: red; lineOpacity: 0.5"></a-entity>The line length is the raycaster's far property when the raycaster is not
intersecting any entity. By default, the far property defaults to 1000 meters
meaning the line drawn will be 1000 meters long. When the raycaster intersects
an object, the line will get truncated to the intersection point so it doesn't
shoot straight through.
Components Real World Meshing (`docs/components/real-world-meshing.md`)
title: real-world-meshing type: components layout: docs parent_section: components source_code: src/components/scene/real-world-meshing.js examples: []
Set this component on the scene element to render meshes corresponding to 3D surfaces detected in user's environment: this includes planes and meshes corresponding to floor, ceiling, walls and other objects. Each plane or meshes comes with a label indicating the type of surface or object.
This component requires a browser with support for the WebXR Mesh Detection Module and the WebXR Plane Detection Module. The system / headset used might require additional scene setup by the use like setting up floor, walls, ceiling or labeling furniture in the space.
Example
<a-scene real-world-meshing></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| filterLabels | List of labels corresponding to the surfaces that will be rendered. Can constrain rendering to certain surfaces like desks, walls, tables... All surfaces will be rendered if left empty. | [] |
| meshesEnabled | If meshes will be rendered as returned by the WebXR Mesh Detection Module. | true |
| meshMixin | Mixin applied to the entities corresponding to the detected meshes. | '' |
| planesEnabled | If planes will be rendered as returned by the WebXR Plane Detection Module. | true |
| planeMixin | Mixin applied to the entities corresponding to the detected planes. | '' |
Components Reflection (`docs/components/reflection.md`)
title: reflection type: components layout: docs parent_section: components source_code: src/components/scene/reflection.js examples: []
The reflection component generates a default environment cube map for all
materials, this is useful in case you find GLB models end up too dark or reflective
materials don't look right because they are not reflecting the environment this
will provide a default reflective environment.
Scene Lighting and Lighting Estimation
The reflection component will generate an environment map from your surroundings using WebXR
Lighting estimation in Augmented Reality if it is available.
During this it will also take control of your scene's main directional light to ensure it's direction and color matches that of the rest of the environment. This works really well for your scene's shadows.
It will also create a probe light to match the lighting of the real world.
So you should turn off any additional global scene lights, such as other directional lights, hemisphere lights or ambient lights, when the user enters AR. You can do this with the hide-on-enter-ar component.
Once the user leaves AR this light may have a different color, intensity and position than when they entered AR as it has been altered by the lighting estimation.
Example
The example below sets the reflection color to red and use lighting estimation for AR.
<a-scene reflection="directionalLight:a-light#dirlight;">
<a-light id="dirlight" intensity="1" light="castShadow:true;type:directional" position="1 1 1"></a-light>
</a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| directionalLight | Light to control during WebXR Lighting Estimation |
Components Renderer (`docs/components/renderer.md`)
title: renderer type: components layout: docs parent_section: components source_code: src/systems/renderer.js examples: []
The renderer system configures a scene's
THREE.WebGLRenderer instance.
It also configures presentation attributes when entering WebVR/WebXR.
Example
<a-scene renderer="antialias: true;
colorManagement: true;
sortTransparentObjects: true;
maxCanvasWidth: 1920;
maxCanvasHeight: 1920;"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| antialias | Whether to perform antialiasing. If auto, antialiasing is disabled on mobile. |
auto |
| colorManagement | Whether to use a color-managed linear workflow. | true |
| highRefreshRate | Increases frame rate from the default (for browsers that support control of frame rate). | false |
| foveationLevel | Amount of foveation used in VR to improve perf, from 0 (min) to 1 (max). | 1 |
| sortTransparentObjects | Whether to sort transparent objects (far to near) before rendering | false |
| maxCanvasWidth | Maximum canvas width. Uses the size multiplied by device pixel ratio. Does not limit canvas width if set to -1. | -1 |
| maxCanvasHeight | Maximum canvas height. Behaves the same as maxCanvasWidth. | -1 |
| multiviewStereo | Enables the use of the OCULUS_multiview extension. | false |
| logarithmicDepthBuffer | Whether to use a logarithmic depth buffer. | auto |
| precision | Fragment shader precision : low, medium or high. | high |
| alpha | Whether the canvas should contain an alpha buffer. | true |
| stencil | Whether the canvas should contain a stencil buffer. | false |
| toneMapping | Type of toneMapping to use, one of: 'no', 'ACESFilmic', 'linear', 'reinhard', 'cineon', 'AgX', 'neutral' | 'no' |
| exposure | When any toneMapping other than "no" is used this can be used to make the overall scene brighter or darker | 1 |
| anisotropy | Default anisotropic filtering sample rate to use for textures | 1 |
NOTE: Once the scene is initialized, none of these properties may no longer be changed apart from "exposure", "toneMapping", "sortTransparentObjects" and "foveationLevel" which can be set dynamically.
antialias
When enabled, smooths jagged edges on curved lines and diagonals at moderate performance cost.
By default, antialiasing is disabled on mobile devices.
colorManagement
Color management provides more accurate rendering and reduces the likelihood that scenes
will appear overlit or "washed out." Enabling color management is recommended for precisely
matching colors from texturing and modeling tools, but unofficial components may not always
respond to color management properly at this time.
Managed and unmanaged color modes are similar to linear and gamma workflows, respectively, in
other engines and tools.
NOTE: In three.js, and previous versions of A-Frame, a
gammaOutput: trueproperty was
available. This is applied automatically when color management is enabled.
highRefreshRate
Switches to a higher frame rate than the default, for the given device.
This requires support for supportedFrameRates and updateTargetFrameRate as defined in the WebXR specs. Currently this is supported on the Oculus Browser, but is expected to be supported by other browsers in future.
Frame rates used are as follows:
| Device capabilities | Default Frame Rate | High Frame Rate |
|---|---|---|
| Device supports 90Hz refresh rate (e.g. Quest 2, Quest Pro) | 72 Hz | 90 Hz |
| Device does not support 90Hz refresh rate (e.g. Oculus Go, Quest) | 60Hz | 72Hz |
foveationLevel
Controls the amount of foveation which renders fewer pixels near the edges of the user's field of view
when in stereo rendering mode on certain systems. The value should be in the range of 0 to 1, where
0 is the minimum and 1 the maximum amount of foveation. This is currently supported by the Oculus Browser.
sortTransparentObjects
Sorting is used to attempt to properly render objects that have some degree of transparency.
Due to various limitations, proper transparency often requires some amount of careful setup.
By default, transparent objects are not sorted, and the order of elements in the DOM determines order of
rendering. Re-ordering DOM elements provides one way of forcing a consistent behavior, whereas
use of renderer="sortTransparentObjects: true" may cause unwanted changes as the camera moves.
Some more background on how A-Frame sorts objects for rendering can be found here
logarithmicDepthBuffer
A logarithmic depth buffer may provide better sorting and rendering in scenes containing very
large differences of scale and distance.
Precision
Set precision in fragment shaders. Main use is to address issues in older hardware / drivers. Adreno 300 series GPU based phones are particularly problematic. You can set to mediump as a workaround. It will improve performance, in mobile in particular but be aware that might cause visual artifacts in shaders / textures.
alpha
Whether the canvas should contain an alpha buffer. If this is true the renderer will have a transparent backbuffer and the canvas can be composited with the rest of the webpage. See here for more info.
multiviewStereo
Performance improvement for applications that are CPU limited and draw count bound. Most experiences will get a free perf gain from this extension at not visual cost but there are limitations to consider. multiview builds on the multisampled render to texture extension that discards the frame buffer if there are other texture operations during rendering. Problem outlined in https://github.com/KhronosGroup/WebGL/issues/2912. Until browsers and drivers allow more control of when multisample is resolved we have a workaround with some drawbacks. As a temporary solution when enabling multiview the upload of texture data is deferred until the rendering of the main scene has ended, adding one extra frame of latency to texture uploads. Scenarios affected are for example skeletal meshes that upload bone textures with TexImage. With the workadound in place all bone animations will lag by one frame. Another issue is rendering mirror reflexions or rendering another view in the middle of the scene. The logic would have to move to the beginning of the frame to make sure it's not interrupted by the multiview frame. Because of the limitations this flag is disabled by default so developers can address any issues before enabling.
Components Rotation (`docs/components/rotation.md`)
title: rotation type: components layout: docs parent_section: components source_code: src/components/rotation.js examples: []
The rotation component defines the orientation of an entity in degrees. It
takes the pitch (x), yaw (y), and roll (z) as three space-delimited
numbers indicating degrees of rotation.
All entities inherently have the rotation component.
Example
<a-entity rotation="45 90 180"></a-entity>Value
A-Frame uses a right-handed coordinate system. When aligning our right hand's
thumb with a positive axis, our hand will curl in the positive direction of
rotation.
| Axis | Description | Default Value |
|---|---|---|
| x | Pitch, rotation about the X-axis. | 0 |
| y | Yaw, rotation about the Y-axis. | 0 |
| z | Roll, rotation about the Z-axis. | 0 |
Relative Rotation
Child entities inherit world-space rotations from parent entities.
Consider this scene:
<a-entity id="parent" rotation="0 45 0">
<a-entity id="child1"></a-entity>
<a-entity id="child2" rotation="15 45 30"></a-entity>
</a-entity>The world-space rotation of #child1 would be 0 45 0 as inherited by the
entity. In the local parent's space, #child1's rotation would be 0 0 0.
The world-space rotation of #child2 would be 15 90 30, by combining the
rotation with the parent entity. In the parent's local space, #child2's
rotation would be 15 45 30.
Updating Rotation
For performance and ergonomics, we recommend updating rotation directly via the
three.js Object3D .rotation Euler (in radians) versus
via .setAttribute.
This method is easier because we have access to all the Euler
utilities, and faster by skipping .setAttribute overhead and not
needing to create an object to set rotation:
// With three.js
el.object3D.rotation.set(
THREE.MathUtils.degToRad(15),
THREE.MathUtils.degToRad(30),
THREE.MathUtils.degToRad(90)
);
el.object3D.rotation.x += Math.PI;
// With .setAttribute (less recommended).
el.setAttribute('rotation', {x: 15, y: 30, z: 90});Getting Rotation
Updates at the three.js level will still be reflected in A-Frame when doingentityEl.getAttribute('rotation');. When calling .getAttribute('rotation'),
A-Frame will convert from radians to degrees and return a normal JavaScript
object with x/y/z properties.
See also reading position and rotation of the camera.
Order of Transformations
Transformations are applied to entities in this order:
- scale
- rotation
- position/translation
Components Scale (`docs/components/scale.md`)
title: scale type: components layout: docs parent_section: components source_code: src/components/scale.js examples: []
The scale component defines a shrinking, stretching, or skewing transformation
of an entity. It takes three scaling factors for the X, Y, and Z axes.
All entities inherently have the scale component.
Example
The example below shrinks the entity in half along the X direction, maintains
the same scale factor along the Y direction, and stretches the entity by
two-fold along the Z-direction:
<a-entity scale="0.5 1 2"></a-entity>Value
If we set any of the scaling factors to 0, then A-Frame will assign instead a
negligible value.
| Axis | Description | Default Value |
|---|---|---|
| x | Scaling factor in the X direction. | 1 |
| y | Scaling factor in the Y direction. | 1 |
| z | Scaling factor in the Z direction. | 1 |
Reflection
Scaling factors can be negative, which results in a reflection.
A notable use for this is for sky spheres. Sky spheres contain the entire scene
and have a texture mapped on the interior surface. To do this, we can reflect,
or invert, the sphere in the Z-direction.
<a-entity geometry="primitive: sphere; radius: 1000"
material="src: sky.png"
scale="1 1 -1"></a-entity>Relative Scale
Similar to the rotation and position components, scales are applied in the
local coordinate system and multiply in nested entities.
Updating Scale
For performance and ergonomics, we recommend updating scale directly via the
three.js Object3D .scale Vector3 versus via.setAttribute.
This method is easier because we have access to all the Vector3
utilities, and faster by skipping .setAttribute overhead and not
needing to create an object to set rotation:
// With three.js
el.object3D.scale.set(1, 2, 3);
// With .setAttribute (not recommended).
el.setAttribute('scale', {x: 1, y: 2, z: 3});Also easier to do incremental updates:
el.object3D.scale.x += 1;
el.object3D.scale.multiplyScalar(2);
el.object3D.scale.sub(someOtherVector);Getting Scale
To reflect updates done at the three.js level, A-Frame returns the actualObject3D.scale vector object when doing .getAttribute('scale'). Note
modifying the return value will modify the entity itself.
Order of Transformations
Transformations are applied to entities in this order:
- scale
- rotation
- position/translation
Components Screenshot (`docs/components/screenshot.md`)
title: screenshot type: components layout: docs parent_section: components source_code: src/components/scene/screenshot.js examples: []
The screenshot component lets us take different types of screenshots with
keyboard shortcuts. A-Frame attaches this component to the scene by default so
we don't have to do anything to use the component.
Shortcuts
Equirectangular Screenshot
To take a 360° (equirectangular) screenshot, press <ctrl> + <alt> + <shift> + s
on the keyboard.
Perspective Screenshot
To take a normal (perspective) screenshot, press <ctrl> + <alt> + s on the
keyboard.
Properties
| Property | Description | Default Value |
|---|---|---|
| width | The width in pixels of the screenshot taken. | 4096 |
| height | The height in pixels of the screenshot taken. | 2048 |
Methods
To take a screenshot programmatically and get a canvas, call getCanvas():
// `screenshot.projection` property can be `equirectangular` or `perspective`.
document.querySelector('a-scene').components.screenshot.getCanvas('equirectangular');To take a screenshot programmatically and automatically save the file, call capture():
document.querySelector('a-scene').components.screenshot.capture('perspective')Components Shadow (`docs/components/shadow.md`)
title: shadow type: components layout: docs parent_section: components source_code: src/components/shadow.js examples: []
The shadow component enables shadows for an entity and its children. Receiving
shadows from surrounding objects and casting shadows onto other objects may
(and often should) be enabled independently.
Without this component, an entity will not cast nor receive shadows.
Example
The example below configures a tree model to cast shadows onto the surrounding
scene but not receive shadows itself.
<a-entity light="type:directional; castShadow:true;" position="1 1 1"></a-entity>
<a-gltf-model src="tree.gltf" shadow="receive: false"></a-gltf-model>IMPORTANT: Adding the shadow component alone is not enough to display
shadows in your scene. We must have at least one light with castShadow: true enabled. Additionally, the light's shadow camera (used for depth
projection) usually must be configured correctly. Refer to the light
component for more information.
Properties
| Property | Description | Default Value |
|---|---|---|
| cast | Whether the entity casts shadows onto the surrounding scene. | true |
| receive | Whether the entity receives shadows from the surrounding scene. | true |
Scene Properties
The shadow system exposes scene-level properties for configuring the renderer
for shadows. These are set on <a-scene> (e.g., <a-scene shadow="autoUpdate: false">).
| Property | Description | Default Value |
|---|---|---|
| enabled | Whether to disable shadows globally, even if there is a shadow component and a light with castShadow: true enabled. |
true |
| autoUpdate | Whether to dynamically update the shadow map every frame. Disable and manually update by setting renderer.shadowMap.needsUpdate = true for best performance. Calculating shadow maps is expensive. |
true |
| type | Shadow type. One of pcf, basic. |
pcf (percentage closer filtering) |
Components Sound (`docs/components/sound.md`)
title: sound type: components layout: docs parent_section: components source_code: src/components/sound.js examples: []
The sound component defines the entity as a source of sound or audio. The sound
component is positional and is thus affected by the
components-position.
NOTE: Playing sound on iOS — in any browser — requires a physical user interaction.
This is a browser limitation, and internal A-Frame events (like fusing cursors) do
not count as interaction. Ways to deal with this include using a Begin Experience
button to start ambient music, or creating audio sprites with libraries like
Howler.js.
Example
<a-entity id="river" geometry="primitive: plane" material="color: blue"
position="-10 0 0" sound="src: url(river.mp3); autoplay: true"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| autoplay | Whether to automatically play sound once set. | false |
| distanceModel | linear, inverse, or exponential |
inverse |
| loop | Whether to loop the sound once the sound finishes playing. | false |
| maxDistance | Maximum distance between the audio source and the listener, after which the volume is not reduced any further. | 10000 |
| on | An event for the entity to listen to before playing sound. | null |
| stopOn | An event for the entity to listen to before stopping sound. | null |
| pauseOn | An event for the entity to listen to before pausing sound. | null |
| poolSize | Numbers of simultaneous instances of this sound that can be playing at the same time | 1 |
| positional | Whether or not the audio is positional (movable). | true |
| refDistance | Reference distance for reducing volume as the audio source moves further from the listener. | 1 |
| rolloffFactor | Describes how quickly the volume is reduced as the source moves away from the listener. | 1 |
| src | Selector to an asset <audio>or url()-enclosed path to sound file. |
null |
| volume | How loud to play the sound. | 1 |
Methods
| Event Name | Description |
|---|---|
| pauseSound | Pause sound. |
| playSound | Play sound. |
| stopSound | Stop sound. |
Events
| Event Name | Description |
|---|---|
| sound-loaded | Triggered when sound file is loaded. Event detail will contain the sound's name and id. |
| sound-ended | Triggered when sound finishes playing. Event detail will contain the sound's name and id. |
Playing on an Event
The sound component can also listen to an event before playing as well. For
example, we might have a laughing sound play every time we click a monster:
<a-entity cursor position="0 0 -5"></a-entity>
<a-entity id="elmo" geometry="primitive: box" material="src: elmo.png"
sound="src: url(ticklelaugh.mp3); on: click"></a-entity>Stopping on an Event
The sound component can also listen to an event before stopping the sound,
using the stopOn property. For example, we might play music when the user
enters a zone and stop it when they leave:
<a-entity sound="src: url(music.mp3); on: zone-enter; stopOn: zone-leave"></a-entity>Similarly, the pauseOn property pauses the sound on an event. Unlike stopOn,
a sound paused this way resumes from where it left off the next time it is
played:
<a-entity sound="src: url(music.mp3); on: zone-enter; pauseOn: menu-open"></a-entity>Preloading a Sound Asset
For performance, we recommend to block the scene on the sound asset to preload
and cache. We can do so through the asset management system:
<a-scene>
<a-assets>
<audio id="river" src="river.mp3" preload="auto"></audio>
</a-assets>
<a-entity sound="src: #river"></a-entity>
</a-scene>Pause and Resume
To programmatically pause or resume a playing sound, we can tell the entity to
pause or resume:
var entity = document.querySelector('[sound]');
entity.components.sound.stopSound();Or to pause only the sound:
entity.components.sound.pauseSound();And to play the sound:
entity.components.sound.playSound();Components Stats (`docs/components/stats.md`)
title: stats type: components layout: docs parent_section: components source_code: src/components/scene/stats.js
The stats component displays a UI with performance-related metrics. The stats
component applies only to the <a-scene> element.
Example
<a-scene stats></a-scene>Metrics
- fps: frames per second, framerate. Aim for stable 90 fps with the WebVR 1.0 API.
- requestAnimationFrame (raf): Latency.
- Textures: number of three.js textures in the scene. A lower count means
the scene is using less memory and sending less data to the GPU. - Programs: number of GLSL shaders in the scene.
- Geometries: number of three.js geometries in the scene. A lower count
means the scene is using less memory. - Vertices: number of vertices in the scene.
- Faces: number of faces in the scene.
- Calls: number of draw calls on each frame.
- Load Time: how long it took for the scene to start rendering, in ms.
- Entities: number of A-Frame entities.
Toggling UI
Click or tap on the headings to collapse groups of metrics. If you tap
"Framerate", then fps and raf will collapse.
Components Text (`docs/components/text.md`)
title: text
type: components
layout: docs
parent_section: components
source_code: src/components/text.js
examples:
- title: Stock Font Showcase
src: https://glitch.com/~aframe-text-fonts-demo
The text component renders signed distance field (SDF) font text.

Introduction
Note that rendering text in 3D is hard. In 2D web development, text is the most
basic thing because the browser's renderer and layout engine handle
everything. In a 3D context, we don't have those luxuries. There are several
other different ways to render text in A-Frame including:
- 3D Text Geometry
- HTML Materials (DOM-to-Canvas-to-Texture)
- Image Textures
As a default, we've selected SDF-based text to be included as a core component
due to its relatively good performance and clarity. This component uses
mattdesl's three-bmfont-text library. The standard text
component has a long lineage, starting out as a community component and was
forked and improved several times before landing into A-Frame!
Example
Here's a basic example of text defining just the content with not much other
configuration.
<a-entity text="value: Hello World;"></a-entity>See more examples to see configuration of alignments, anchors, baselines,
scaling, and auto-sizing.
Open any of these example scenes, hit <ctrl> + <alt> + i to open the
Inspector, and play with all the possible values to see the effects instantly!

Properties
| Property | Description | Default Value |
|---|---|---|
| align | Multi-line text alignment (left, center, right). | left |
| alphaTest | Discard text pixels if alpha is less than this value. | 0.5 |
| anchor | Horizontal positioning (left, center, right, align). | center |
| baseline | Vertical positioning (top, center, bottom). | center |
| color | Text color. | white |
| font | Font to render text, either the name of one of A-Frame's stock fonts or a URL to a font file | default |
| fontImage | Font image texture path to render text. Defaults to the font's name with extension replaced to .png. Don't need to specify if using a stock font. |
derived from font name |
| height | Height of text block. | derived from text size |
| letterSpacing | Letter spacing in pixels. | 0 |
| lineHeight | Line height in pixels. | derived from font file |
| opacity | Opacity, on a scale from 0 to 1, where 0 means fully transparent and 1 means fully opaque. | 1.0 |
| shader | Shader used to render text. | sdf |
| side | Side to render. (front, back, double) | front |
| tabSize | Tab size in spaces. | 4 |
| transparent | Whether text is transparent. | true |
| value | The actual content of the text. Line breaks and tabs are supported with \n and \t. |
'' |
| whiteSpace | How whitespace should be handled (i.e., normal, pre, nowrap). Read more about whitespace. | normal |
| width | Width in meters. | derived from geometry if exists |
| wrapCount | Number of characters before wrapping text (more or less). | 40 |
| wrapPixels | Number of pixels before wrapping text. | derived from wrapCount |
| xOffset | X-offset to apply to add padding. | 0 |
| zOffset | Z-offset to apply to avoid Z-fighting if using with a geometry as a background. | 0.001 |
The implementation is based on mattdesl's three-bmfont-text.
Read more about the text properties.
Events
| Event Name | Description |
|---|---|
| textfontset | Emitted when the font source has been loaded |
Fonts
We can specify different fonts, although the process is not as simple as Web
Fonts. The text component defaults to roboto which uses a multi-channel
signed distance (MSDF) font. MSDF helps to preserve sharp corners and edges.
Stock Fonts
Select from one of A-Frame's built-in fonts. These fonts will be loaded in from
over a CDN. If you want your application to work better offline, download these
fonts locally and point to them via a URL.
| Stock MSDF Fonts | URL |
|---|---|
| roboto | https://cdn.aframe.io/fonts/Roboto-msdf.json |
| Stock SDF Fonts | URL |
|---|---|
| aileronsemibold | https://cdn.aframe.io/fonts/Aileron-Semibold.fnt |
| dejavu | https://cdn.aframe.io/fonts/DejaVu-sdf.fnt |
| exo2bold | https://cdn.aframe.io/fonts/Exo2Bold.fnt |
| exo2semibold | https://cdn.aframe.io/fonts/Exo2SemiBold.fnt |
| kelsonsans | https://cdn.aframe.io/fonts/KelsonSans.fnt |
| monoid | https://cdn.aframe.io/fonts/Monoid.fnt |
| mozillavr | https://cdn.aframe.io/fonts/mozillavr.fnt |
| sourcecodepro | https://cdn.aframe.io/fonts/SourceCodePro.fnt |
Custom Fonts
Different fonts can be specified using the font and fontImage properties.
<a-entity text="font: mozillavr; value: Via stock font name."></a-entity>
<a-entity text="font: https://cdn.aframe.io/fonts/mozillavr.fnt; value: Via URL."></a-entity>
<a-entity text="text: Hello World; font: ../fonts/DejaVu-sdf.fnt; fontImage: ../fonts/DejaVu-sdf.png"></a-entity>If not specified, fontImage will be the font's name, but with the extension
replaced to .png. For example, if the font path ends with mozillavr.fnt,
then the fontImage texture will default to mozillavr.png.
Generating SDF Fonts
On top of the stock fonts, we can generate SDF fonts using Hiero, a
bitmap font packing tool. See this guide for generating SDF fonts.
We can also generate MSDF fonts using a web-based MSDF tool, or on the commandline with msdfgen or msdf-bmfont. Tools for MSDF fonts may be less mature than the SDF alternatives.
Below is an example comparing a font generated with Hiero, Arial Black, with
the SDF font, DejaVu:

Non-ASCII Characters
To use non-ascii characters, you need to create your own custom font. The easiest way is to use a web-based MSDF tool. Select your required character set and generate your own custom msdf font zip file.
Here is an example of a French character set you can use:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.?!/:;,*§£$ø@+°-~#&²'{}[]|`\()=%*µ àâéèëêïîöôùüûÀÂÉÈËÊÏÎÖÔÜÛçÇ€Once you download your custom msdf font zip file, extract it, then put both png and json files to your A-Frame directory.
From A-Frame 0.9.0 and above, you don't need to rename *.png to *-msdf.png anymore, A-Frame loads the image defined in the json file.
Lastly, you should specify the character set used in your HTML by using <meta> tag to avoid text to be garbled. If your text is garbled, it is not rendered.
<html>
<head>
<meta charset="UTF-8">
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-sky color="lightblue"></a-sky>
<a-text value="ABCあいうえお日本語" font="custom-msdf.json" font-image="custom-msdf.png" negate="false" scale="2 2 1" position="-2 2 -4"></a-text>
</a-scene>
</body>
</html>Sizing
To change the size of the text, we can:
- Change the
width. - Change the
wrapCount(roughly how many characters to fit inside the given width). - Change
wrapPixels. - Change the scale component.
- Position the text closer or farther away.

Text can be wrapped by specifying width in A-Frame units.
In case we need to do custom layout or need to know the bounds of the text, the
output length of the text can be pre-calculated dynamically with something
like:
totalWidth = data.value.length * (data.width / data.wrapCount)Auto-Scaling
The text component introduces special behavior when using alongside the
geometry component (e.g., a background plane) to fit. Note this only works with
2D-friendly geometries that define a width and height (i.e., box, plane).
The text can either be scaled, bounded, or aligned in relation to the geometry,
or the text can auto-scale the geometry to fit the text.

Scaling Text to Fit Geometry
To have the text component's width property automatically scale to match the
geometry component's width, do not specify a width for the text component:
<a-entity
geometry="primitive: plane; width: 4; height: 0"
material="color: blue"
text="value: This text will be 4 units wide."></a-entity>Scaling Geometry to Fit Text
To have the geometry automatically scale with the text, set the geometry
component's width and height properties to 0, and set the text
component's width as desired. In this example, the plane's width will be
set to 4 units, and its height will be set to match the actual height of the
text:
<a-entity
geometry="primitive: plane; height: 0; width: 0"
material="color: blue"
text="width: 4; value: This text will be 4 units wide."></a-entity>Note that if neither geometry nor text specify a width, the text width
property will default to 1 unit (meter), and the geometry width property will
then become 1 unit as well.
<a-entity
geometry="primitive: plane"
material="color: blue"
text="value: 1-wide\ndefault."></a-entity>Limitations
The text component does not make use of all of the features of thethree-bmfont-text library nor its sister modules.
Bitmap font rendering limits you to the characters included in the font
(Unicode this is not). SDF font rendering tends to produce smooth sharp edges
though there are ways around this.
The generated text is not suitable for raycaster intersection testing. For raycaster or cursor detection it is necessary to use a geometry component along with the text component.
Further Reading
If you are curious about the details of text rendering in WebGL, three.js, and
A-Frame, below are links to some background reading:
three-bmfont-textdocumentation- Hacking with three.js
- It's 2015 and drawing text is still hard (WebGL, ThreeJS)
- Improved Alpha-Tested Magnification for Vector Textures and Special Effects
Components Tracked Controls (`docs/components/tracked-controls.md`)
title: tracked-controls type: components layout: docs parent_section: components source_code: src/components/tracked-controls.js examples: []
The tracked-controls component interfaces with tracked controllers.
tracked-controls uses the Gamepad API to handle tracked controllers, and is
abstracted by the hand-controls component as well as the
vive-controls, meta-touch-controls,
windows-motion-controls components.
This component elects the appropriate controller, applies pose to
the entity, observes buttons state and emits appropriate events. For non-6DOF controllers,
a primitive arm model is used to emulate positional data.
Example
Note that due to recent browser-specific changes, Vive controllers may be returned
by the Gamepad API with id values of either "OpenVR Gamepad" or "OpenVR Controller",
so using idPrefix for Vive / OpenVR controllers is recommended.
<a-entity tracked-controls="controller: 0; idPrefix: OpenVR"></a-entity>Value
| Property | Description | Default Value |
|---|---|---|
| autoHide | Whether to toggle visibility automatically when controller is connected or disconnected. | true |
| controller | Index of the controller in array returned by the Gamepad API. | 0 |
| id | Selects the controller from the Gamepad API using exact match. | |
| idPrefix | Selects the controller from the Gamepad API using prefix match. | |
| headElement | Head element for arm model if needed (if not active camera). | |
| hand | Which hand to use, if arm model is needed. (left negates X) | right |
| space | Specifies whether to use targetRayspace or gripSpace to determine controller pose. | targetRaySpace |
Events
| Event Name | Description |
|---|---|
| controllerconnected | Controller connected and set up. |
| controllerdisconnected | Controller disconnected. |
| axismove | Axis changed (e.g., for thumbstick, touchpad). Contains axis and axesChanged in the event detail. axis is an array of values from -1.0 (left, down) to 1.0 (right, up). |
| buttonchanged | Any touch or press of a button fires this. |
| buttondown | Button pressed. |
| buttonup | Button released. |
| touchstart | Touch sensitive button touched. |
| touchend | Touch sensitive button released. |
More Resources
- Gamepad API - W3C Gamepad API
- OpenVR - OpenVR Documentation
Components Visible (`docs/components/visible.md`)
title: visible type: components layout: docs parent_section: components source_code: src/components/visible.js examples: []
The visible component determines whether to render an entity. If set tofalse, then the entity will not be visible nor drawn.
Visibility effectively applies to all children. If an entity's parent or
ancestor entity has visibility set to false, then the entity will also not be
visible nor draw. It's a common pattern to create container entities that
contain an entire group of entities that you can flip on an off with visible.
Example
<a-entity visible="false"></a-entity>Value
| Value | Description |
|---|---|
| true | The entity will be rendered and visible; the default value. |
| false | The entity will not be rendered nor visible. The entity will still exist in the scene. |
Updating Visibility
It is slightly faster to update visibility at the three.js level versus via.setAttribute.
// With three.js
el.object3D.visible = false;
// With .setAttribute.
el.setAttribute('visible', false);Updates at the three.js level will still be reflected when doingentityEl.getAttribute('visible');.
Hiding Entity Until Texture Loaded
While we can do this with the event-set component, we can also do
this with the built-in animation component. Here's an example of updating
visibility on an event.
<a-entity
animation="property: visible; to: true; startEvents: materialtextureloaded"></a-entity>
material="src: #myTexture"
visible="false">Components Vive Controls (`docs/components/vive-controls.md`)
title: vive-controls type: components layout: docs parent_section: components source_code: src/components/vive-controls.js examples: []
The vive-controls component interfaces with the HTC Vive controllers/wands. It
wraps the tracked-controls component while adding button
mappings, events, and a Vive controller model that highlights the pressed
buttons (trigger, grip, menu, system) and trackpad.
Example
<a-entity vive-controls="hand: left"></a-entity>
<a-entity vive-controls="hand: right"></a-entity>Value
| Property | Description | Default Value |
|---|---|---|
| buttonColor | Button colors when not pressed. | #FAFAFA (off-white) |
| buttonHighlightColor | Button colors when pressed and active. | #22D1EE (light blue) |
| hand | The hand that will be tracked (i.e., right, left). | left |
| model | Whether the Vive controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| gripdown | Grip button pressed. |
| gripup | Grip button released. |
| gripchanged | Grip button changed. |
| menudown | Menu button pressed. |
| menuup | Menu button released. |
| menuchanged | Menu button changed. |
| systemdown | System button pressed. |
| systemup | System button released. |
| systemchanged | System button changed. |
| trackpaddown | Trackpad pressed. |
| trackpadup | Trackpad released. |
| trackpadchanged | Trackpad button changed. |
| triggerdown | Trigger pressed. |
| triggerup | Trigger released. |
| triggerchanged | Trigger changed. |
Assets
Components Vive Focus Controls (`docs/components/vive-focus-controls.md`)
title: vive-focus-controls
type: components
layout: docs
parent_section: components
source_code: src/components/vive-focus-controls.js
examples: []
The vive-focus-controls component interfaces with the Vive Focus controller.
It wraps the tracked-controls component while adding button
mappings, events, and an Vive Focus controller model that highlights the touched
and/or pressed buttons (trackpad, trigger).
Example
<a-entity vive-focus-controls></a-entity>
<a-entity vive-focus-controls="hand: left"></a-entity>
<a-entity vive-focus-controls="hand: right"></a-entity>Value
| Property | Description | Default |
|---|---|---|
| buttonTouchedColor | Button colors when touched (Trackpad only). | #777777 |
| buttonHighlightColor | Button colors when pressed and active. | #FFFFFF |
| hand | The hand that will be tracked (e.g., right, left). | |
| model | Whether the Vive Focus controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| trackpadchanged | Trackpad changed. |
| trackpaddown | Trackpad pressed. |
| trackpadmoved | Trackpad axis changed. |
| trackpadup | Trackpad released. |
| trackpadtouchstart | Trackpad touched. |
| trackpadtouchend | Trackpad not touched. |
| triggerchanged | Trigger changed. |
| triggerdown | Trigger pressed. |
| triggerup | Trigger released. |
As this controller's buttons are digital, the changed events only fire when
a button is fully pressed or released (value 0 or 1).
Assets
Components Wasd Controls (`docs/components/wasd-controls.md`)
title: wasd-controls type: components layout: docs parent_section: components source_code: src/components/wasd-controls.js
The wasd-controls component controls an entity with the WASD or arrow keyboard
keys. The wasd-controls component is commonly attached to an entity with the camera
component.
Example
<a-entity camera look-controls wasd-controls="acceleration:100" position="0 1.6 0"></a-entity>Properties
| Property | Description | Default Value |
|---|---|---|
| acceleration | How fast the entity accelerates when holding the keys. | 65 |
| adAxis | Axis that the A and D keys act upon. |
x |
| adInverted | Whether the axis that the A and D keys act upon are inverted. |
false |
| enabled | Whether the WASD controls are enabled. | true |
| fly | Whether or not movement is restricted to the entity's initial plane. | false |
| wsAxis | Axis that the W and S keys act upon. |
z |
| wsInverted | Whether the axis that the W and S keys act upon are inverted. | false |
Components Webxr (`docs/components/webxr.md`)
title: webxr type: components layout: docs parent_section: components source_code: src/systems/webxr.js examples: []
The webxr system configures a scene's WebXR device setup,
specifically the options for the requestSession call.
When modifying these options, please pay attention to the browser console for
diagnostic messages such as trying to use features without properly requesting
them.
Example
<a-scene webxr="requiredFeatures: hit-test,local-floor;
optionalFeatures: dom-overlay,unbounded;
overlayElement: #overlay;"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| referenceSpaceType | The scene's reference space type for camera and controllers. | local-floor |
| requiredFeatures | Required WebXR session features. | local-floor |
| optionalFeatures | Optional WebXR session features. | bounded-floor |
| overlayElement | Element selector for use as a WebXR DOM Overlay in AR mode. | null |
NOTE: Once the scene is initialized, these properties may no longer be changed.
referenceSpaceType
Name of the reference space used by default for the scene, must be one of the
entries in reference space names.
| Name | Description |
|---|---|
| viewer | Rigidly attached to the camera and moves/rotates with it |
| local | Origin is an arbitrary point close to the user's head location at session start. |
| local-floor | Origin is an arbitrary point close to the user's feet at session start. |
| bounded-floor | Same as local-floor, but supports room-scale tracking with safety bounds. |
| unbounded | Same as local, but supports large-scale movement beyond ~5 meters / 15 feet. |
The default 'local-floor' should work reasonably well on all systems, but please
be aware that the floor position may be a rough estimate for 3DoF VR systems, or
for handheld AR. For AR applications that need accurate floor location, it's
recommended to use type 'local' along with world hit testing or plane detection.
Make sure the reference space name is included in requiredFeatures. ('viewer' and
'local' are automatically available, all others must be requested as features.)
Applications are free to use additional reference spaces internally, but it's
important that the viewer (camera) and controllers are consistent.
For consistency when used in components, this name is available assceneEl.systems.webxr.sessionReferenceSpaceType, and the corresponding
reference space object is available during the XR session assceneEl.systems['tracked-controls'].referenceSpace.
requiredFeatures
Array (comma-separated list) of WebXR feature names that are required for the
application to work correctly. If one of the features is not supported by the
system, or if the user doesn't agree to it being used, the session request will
fail.
Currently, the list of available features includes the reference space
names, and the
following additional features for AR mode:
| Feature | Description |
|---|---|
| hit-test | https://immersive-web.github.io/hit-test/ |
| dom-overlay | https://immersive-web.github.io/dom-overlays/ |
optionalFeatures
Array (comma-separated list) of WebXR feature names that an application can use,
but where the application will still work if they are not supported. The
session will be created without these features if they are unavailable or
if the user declined their use.
See requiredFeatures for more information of available
WebXR feature names.
overlayElement
Selector for a DOM element that should be used as a WebXR DOM Overlay during
an AR session. This is currently only supported for handheld AR using Chrome
on Android version 82 or newer.
You must request the feature dom-overlay to use this, either as an optional or
required feature.
If the feature is available, sceneEl.xrSession.domOverlayMode will have
a string value indicating the type of DOM Overlay being used. This is currentlyscreen for handheld AR, but may also be floating or head-locked on a
headset. Also, the overlay element will have the :xr-overlay pseudoclass
while active, and you can use this in CSS rules to show/hide UI.
Components Windows Motion Controls (`docs/components/windows-motion-controls.md`)
title: windows-motion-controls type: components layout: docs parent_section: components source_code: src/components/windows-motion-controls.js examples: []
The windows-motion-controls component interfaces with any spatial controllers exposed through
Windows Mixed Reality as Spatial Input Sources (such as Motion Controllers).
It wraps the tracked-controls component while adding button
mappings, events, and a controller model that highlights applies position/rotation transforms
to the pressed buttons (trigger, grip, menu, thumbstick, trackpad) and moved axes (thumbstick and trackpad.)
Example
<a-entity windows-motion-controls="hand: left"></a-entity>
<a-entity windows-motion-controls="hand: right"></a-entity>Value
| Property | Description | Default Value |
|---|---|---|
| hand | The hand that will be tracked (i.e., right, left). | right |
| pair | Which pair of controllers, if > 2 are connected. | 0 |
| model | Whether the controller model is loaded. | true |
Events
| Event Name | Description |
|---|---|
| thumbstickdown | Thumbstick button pressed. |
| thumbstickup | Thumbstick button released. |
| thumbstickchanged | Thumbstick button changed. |
| thumbstickmoved | Thumbstick axis moved. |
| triggerdown | Trigger pressed. |
| triggerup | Trigger released. |
| triggerchanged | Trigger changed. |
| gripdown | Grip button pressed. |
| gripup | Grip button released. |
| gripchanged | Grip button changed. |
| menudown | Menu button pressed. |
| menuup | Menu button released. |
| menuchanged | Menu button changed. |
| trackpaddown | Trackpad pressed. |
| trackpadup | Trackpad released. |
| trackpadchanged | Trackpad button changed. |
| trackpadmoved | Trackpad axis moved. |
| controllermodelready | The model file is loaded and completed parsing. |
Assets
TBC.
Components Xr Mode Ui (`docs/components/xr-mode-ui.md`)
title: xr-mode-ui type: components layout: docs parent_section: components source_code: src/components/scene/xr-mode-ui.js
The xr-mode-ui component configures or disables the enter VR and AR buttons. Buttons only display if the browser supports the corresponding modes (AR or VR). The xr-mode-ui component applies only
to the <a-scene> element. If we wish to simply toggle the UI, use CSS instead.
Example
<a-scene xr-mode-ui="enabled: false"></a-scene>Properties
| Property | Description | Default Value |
|---|---|---|
| enabled | Whether or not to display UI related to entering VR. | true |
| enterVRButton | Selector to a custom VR button. On click, the button will enter VR. | '' |
| enterVREnabled | If the VR button is displayed when applicable. | true |
| enterARButton | Selector to a custom AR button. On click, the button will enter AR. | '' |
| enterAREnabled | If the AR button is displayed when applicable. | true |
| XRMode | If the AR button (ar), VR button (vr), or both buttons will be displayed (xr). | vr |
Specifying Custom Enter VR and AR Buttons
<a-scene
xr-mode-ui="enterVRButton: #myEnterVRButton; enterARButton: #myEnterARButton; XRMode: xr;">
<a id="myEnterVRButton" href="#"></a>
<a id="myEnterARButton" href="#"></a>
</a-scene>Core Asset Management System (`docs/core/asset-management-system.md`)
title: Asset Management System type: core layout: docs parent_section: core order: 9 source_code: src/core/a-assets.js examples: []
A-Frame has an asset management system that allows us to place our assets in
one place and to preload and cache assets for better performance. Note the asset
management system is purely for preloading assets. Assets that are set on
entities at runtime could be done via direct URLs to the assets.
Games and rich 3D experiences traditionally preload their assets, such as
models or textures, before rendering their scenes. This makes sure that assets
aren't missing visually, and this is beneficial for performance to ensure
scenes don't try to fetch assets while rendering.
We place assets within <a-assets>, and we place <a-assets> within<a-scene>. Assets include:
<a-asset-item>- Miscellaneous assets such as 3D models and materials<audio>- Sound files- `` - Image textures
<video>- Video textures
The scene won't render or initialize until the browser fetches (or errors out)
all the assets or the asset system reaches the timeout.
Example
We can define our assets in <a-assets> and point to those assets from our
entities using selectors:
<a-scene>
<a-assets>
<a-asset-item id="horse-obj" src="horse.obj"></a-asset-item>
<a-asset-item id="horse-mtl" src="horse.mtl"></a-asset-item>
<a-mixin id="giant" scale="5 5 5"></a-mixin>
<audio id="neigh" src="neigh.mp3"></audio>
<img id="advertisement" src="ad.png">
<video id="kentucky-derby" src="derby.mp4"></video>
</a-assets>
<a-plane src="#advertisement"></a-plane>
<a-sound src="#neigh"></a-sound>
<a-entity geometry="primitive: plane" material="src: #kentucky-derby"></a-entity>
<a-entity mixin="giant" obj-model="obj: #horse-obj; mtl: #horse-mtl"></a-entity>
</a-scene>The scene and its entities will wait for every asset (up until the timeout)
before initializing and rendering.
Cross-Origin Resource Sharing (CORS)
Since A-Frame fetches assets using XHRs, browser security requires the
browser to serve assets with cross-origin resource sharing (CORS)
headers if the asset is on a different domain. Otherwise, we'd have
to host assets on the same origin as the scene.
For other options, GitHub Pages serves everything with CORS headers.
We recommend GitHub Pages as a simple deployment platform. Or another service
that serves files with CORS headers set (such as Amazon S3).
Given that CORS headers are set, <a-assets> will automatically setcrossorigin attributes on media elements (e.g., <audio>, ``,<video>) if it detects the resource is on a different domain.
Preloading Audio and Video
Audio and video assets will only block the scene if we set autoplay or if we
set preload="auto":
<a-scene>
<a-assets>
<audio src="blockus.mp3"></audio>
<video src="loadofblocks.mp4"></video>
<audio src="blocky.mp3" autoplay></audio>
<video src="blockiscooking.mp4" preload="auto"></video>
</a-assets>
</a-scene>Setting a Timeout
We can set a timeout that when reached, the scene will begin rendering and
entities will begin initializing regardless of whether all the assets have
loaded. The default timeout is 3 seconds. To set a different timeout, we just
pass in the number of milliseconds to the timeout attribute:
If some assets are taking a long time to load, we may want to set an
appropriate timeout such that the user isn't waiting all day in case their
network is slow.
<a-scene>
<a-assets timeout="10000">
<img src="bigimage.png">
</a-assets>
</a-scene>Events
Since <a-assets> and <a-asset-item> are nodes in A-Frame, they will emit
the loaded event when they say they have finished loading.
| Event Name | Description |
|---|---|
| loaded | All assets were loaded, or assets timed out. |
| timeout | Assets timed out. |
Load Progress on Individual Assets
``
<a-asset-item> invokes the three.js
FileLoader. We can use<a-asset-item> for any file type. When finished, it will set its data
member with the text response.
| Event Name | Description |
|---|---|
| error | Fetch error. Event detail contains xhr with XMLHttpRequest instance. |
| progress | Emitted on progress. Event detail contains xhr with XMLHttpRequest instance, loadedBytes, and totalBytes. |
| loaded | Asset pointed to by src was loaded. |
``
Images are a standard DOM element so we can listen to the standard DOM events.
| Event Name | Description |
|---|---|
| load | Image was loaded. |
`HTMLMediaElement`
Audio and video assets are HTMLMediaElements. The browser triggers
particular events on these elements; noted here for convenience:
| Event Name | Description |
|---|---|
| error | There was an error loading the asset. |
| loadeddata | Progress. |
| progress | Progress. |
A-Frame uses these progress events, comparing how much time the browser
buffered with the duration of the asset, to detect when the asset becomes loaded.
Specifying Response Type
Content fetched by <a-asset-item> will be returned as plain text. If we want
to use a different response type such as arraybuffer, use <a-asset-item>'sresponse-type attribute:
<a-asset-item response-type="arraybuffer" src="model.gltf"></a-asset-item>How It Works Internally
Every element in A-Frame inherits from <a-node>, the AFRAME.ANode
prototype. ANode controls load and initialization order. For an element to
initialize (whether it be <a-assets>, <a-asset-item>, <a-scene>, or<a-entity>), its children must have already initialized. Nodes initialize
bottom up.
<a-assets> is an ANode, and it waits for its children to load before
it loads. And since <a-assets> is a child of <a-scene>, the scene
effectively must wait for all assets to load. We also added extra load logic to<a-entity> such that they explicitly wait for <a-assets> to load if we have
defined <a-assets>.
<a-asset-item> uses THREE.FileLoader to fetch files. three.js stores the
returned data in THREE.Cache. Every three.js loader inherits fromTHREE.FileLoader, whether they are a ColladaLoader, OBJLoader,ImageLoader, etc. And they all have access and are aware of the centralTHREE.Cache. If A-Frame already fetched a file, A-Frame won't try to fetch it
again.
Thus, since we block entity initialization on assets, by the time entities
load, all assets will have been already fetched. As long as we define<a-asset-item>s, and the entity is fetching files using some formTHREE.FileLoader, then caching will automatically work.
Accessing the `FileLoader` and Cache
To access the three.js FileLoader if we want to listen more closely:
console.log(document.querySelector('a-assets').fileLoader);To access the cache that stores XHR responses:
console.log(THREE.Cache);Core Component (`docs/core/component.md`)
title: Component type: core layout: docs parent_section: core order: 3 source_code: src/core/component.js examples: []
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.
As an abstract analogy, if we define a smartphone as an entity, we might use
components to give it appearance (color, shape), to define its behavior
(vibrate when called, shut down on low battery), or to add functionality
(camera, screen).
Components are roughly analogous to CSS. Like how CSS rules modify the
appearance of elements, component properties modify the appearance, behavior,
and functionality of entities.
Component HTML Form
A component holds a bucket of data in the form of one or more component
properties. Components use this data to modify entities. Consider an engine
component, we might define properties such as horsepower or cylinders.
HTML attributes represent component names and the value of those attributes
represent component data.
Single-Property Component
If a component is a single-property component, meaning its data consists of a
single value, then in HTML, the component value looks like a normal HTML
attribute:
<a-entity position="1 2 3"></a-entity>Multi-Property Component
If a component is a multi-property component, meaning the data consists of
multiple properties and values, then in HTML, the component value resembles
inline CSS styles:
<a-entity light="type: point; color: crimson"></a-entity>Register a Component
`AFRAME.registerComponent (name, definition)`
Register an A-Frame component. We must register components before we use them
anywhere in <a-scene>. Meaning from an HTML file, components should come in
order before <a-scene>.
{string} name- Component name. The component's public API as represented through an HTML attribute name.{Object} definition- Component definition. Contains schema and lifecycle handler methods.
// Registering component in foo-component.js
AFRAME.registerComponent('foo', {
schema: {},
init: function () {},
update: function () {},
tick: function () {},
remove: function () {},
pause: function () {},
play: function () {}
});<html>
<head>
<script src="aframe.min.js"></script>
<script src="foo-component.js"></script>
</head>
<body>
<a-scene>
<a-entity foo></a-entity>
</a-scene>
</body>
</html>Schema
The schema is an object that defines and describes the property or properties
of the component. The schema's keys are the names of the property, and the
schema's values define the types and values of the property (in case of a
multi-property component):
AFRAME.registerComponent('bar', {
schema: {
color: {default: '#FFF'},
size: {type: 'int', default: 5}
}
}<a-scene>
<a-entity bar="color: red; size: 20"></a-entity>
</a-scene>
Image by Ruben Mueller from vrjump.de
Property Types
Property types primarily define how the schema parses incoming data from the
DOM for each property. The parsed data will then be available via the data
property on the component's prototype. Below are A-Frame's built-in property
types:
| Property Type | Description | Default Value |
|---|---|---|
| array | Parses comma-separated values to array (i.e., "1, 2, 3" to ['1', '2', '3']). |
[] |
| asset | For URLs pointing to general assets. Can parse URL out of a string in the form of url(). If the value is an element ID selector (e.g., #texture), this property type will call getElementById and getAttribute('src') to return a URL. The asset property type may or may not change to handle XHRs or return MediaElements directly (e.g., `` elements). |
'' |
| audio | Same parsing as the asset property type. Will possibly be used by the A-Frame Inspector to present audio assets. |
'' |
| boolean | Parses string to boolean (i.e., "false" to false, everything else truthy). |
false |
| color | Currently doesn't do any parsing. Primarily used by the A-Frame Inspector to present a color picker. Also, it is required to use color type for color animations to work. | #FFF |
| int | Calls parseInt (e.g., "124.5" to 124). |
0 |
| map | Same parsing as the asset property type. Will possibly be used by the A-Frame Inspector to present texture assets. |
'' |
| model | Same parsing as the asset property type. Will possibly be used by the A-Frame Inspector to present model assets. |
'' |
| number | Calls parseFloat (e.g., "124.5" to 124.5). |
0 |
| selector | Calls querySelector (e.g., "#box" to <a-entity id="box">). |
null |
| selectorAll | Calls querySelectorAll and converts NodeList to Array (e.g., ".boxes" to [<a-entity class="boxes", ...]), |
null |
| string | Doesn't do any parsing. | '' |
| vec2 | Parses two numbers into an {x, y} object (e.g., 1 -2 to {x: 1, y: -2}. |
{x: 0, y: 0} |
| vec3 | Parses three numbers into an {x, y, z} object (e.g., 1 -2 3 to {x: 1, y: -2, z: 3}. |
{x: 0, y: 0, z: 0} |
| vec4 | Parses four numbers into an {x, y, z, w} object (e.g., 1 -2 3 -4.5 to {x: 1, y: -2, z: 3, w: -4.5}. |
{x: 0, y: 0, z: 0, w: 0} |
Property Type Inference
The schema will try to infer a property type given only a default value:
schema: {default: 10} // type: "number"
schema: {default: "foo"} // type: "string"
schema: {default: [1, 2, 3]} // type: "array"The schema will set a default value if not provided, given the property type:
schema: {type: 'number'} // default: 0
schema: {type: 'string'} // default: ''
schema: {type: 'vec3'} // default: {x: 0, y: 0, z: 0}Custom Property Type
We can also define our own property type by providing a parse and stringify
function in place of a type:
schema: {
// Parse slash-delimited string to an array (e.g., `foo="myProperty: a/b"` to `['a', 'b']`),
// stringify array to string (e.g., `['a', 'b']` to `foo="myProperty: a/b"`)
myProperty: {
default: [],
parse: function (value) {
return value.split('/');
},
stringify: function (value) {
return value.join('/');
}
}
}parse is called when component properties are updated by setAttribute method.stringify is called when DOM is updated by flushToDom method.
Single-Property Schema
A component can either be a single-property component (consisting of one
anonymous value) or a multi-property component (consisting of multiple named
values). A-Frame will infer whether a component is single-property vs.
multi-property based on the structure of the schema.
A single-property component's schema contains type and/or default keys, and
the schema's values are plain values rather than objects:
AFRAME.registerComponent('foo', {
schema: {type: 'int', default: 5}
});<a-scene>
<a-entity foo="20"></a-entity>
</a-scene>Definition Lifecycle Handler Methods
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.

Lifecycle method handlers. Image by Ruben Mueller from vrjump.de
Overview of Methods
| 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. |
| tock | Called on each render loop or tick of the scene after the scene has rendered. Used for post processing effects or other logic that needs to happen after the scene has been drawn. |
| 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. |
Component Prototype Properties
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. Important: Do not modify the data attribute directly. It is updated internally by A-Frame. To modify a component, use setAttribute. |
| this.el | Reference to the entity as an HTML element. |
| this.el.sceneEl | Reference to the scene as an HTML element. |
| this.id | If the component can have multiple instances, 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:
- When the component is statically set on the entity in the HTML file and the page is loaded.
- When the component is set on an attached entity via
setAttribute. - When the component is set on an unattached entity, and the entity is
then attached to the scene viaappendChild.
The init handler is often used to:
- Set up initial state and variables
- Bind methods
- Attach event listeners
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 = this.onIntersection.bind(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:
- After
init ()is called, at the beginning of component's lifecycle. - When the component's properties are updated with
.setAttribute.
The update handler is often used to:
- Do most of the work in making modifications to the entity, using
this.data. - Modify the entity whenever one or more component properties change.
Granular modifications to the entity can be done by diffing 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:
- When the component is removed from the entity via
removeAttribute. - When the entity is detached from the scene (e.g.,
removeChild).
The remove handler is often used to:
- Remove, undo, or clean up all of the component's modifications to the entity.
- Detach event listeners.
For example, when the light component 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:
- On each frame of the render loop.
- On the order of 60 to 120 times per second.
- If the entity or scene is not paused (e.g., the Inspector is open).
- If the entity is still attached to the scene.
The tick handler is often used to:
- Continuously modify the entity on each frame or on an interval.
- Poll for conditions.
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 thetick 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();
}
// ...
});`.tock (time, timeDelta, camera)`
Identical to the tick method but invoked after the scene has rendered.
The tock handler is used to run logic that needs access to the drawn scene before it's pushed into the headset like postprocessing effects.
`.pause ()`
.pause () is called when the entity or scene pauses. The entity can call a
component's pause handler:
- Before the component is removed, before the
removehandler is called. - When the entity is paused with
Entity.pause (). - When the scene is paused with
Scene.pause ()(e.g., the Inspector is opened).
The pause handler is often used to:
- Remove event listeners.
- Remove any chances of dynamic behavior.
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:
- When the component is first attached, after the
updatehandler is called. - When the entity was paused but then resumed with
Entity.play (). - When the scene was paused but then resumed with
Scene.play ().
The play handler is often use to:
- Add event listeners.
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:
- Dynamically update or extend the schema, usually depending on the value of a property.
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);
}
}
// ...
});Definition Properties
`dependencies`
dependencies allows for control on ordering of component initialization if a
component depends on one or more other components. Component names specified in
the dependencies array will be initialized left-to-right before initializing
the current component. If the dependency have other dependency components,
those other dependency components will be ordered in the same manner:
// Initializes last.
AFRAME.registerComponent('a', {
dependencies: ['b']
});
// Initializes second.
AFRAME.registerComponent('b', {
dependencies: ['c']
});
// Initializes first.
AFRAME.registerComponent('c', {});`multiple`
The multiple flag allows for a component to have multiple instances of itself
on an entity. Since multiple is set to false by default, an entity could
only have a single instance of a component. For example, an entity could only
have one geometry component.
But if a component has multiple set to true, then the component can have
multiple instances:
AFRAME.registerComponent('foo', {
multiple: true,
// ...
});In the DOM, we can differentiate between instances of the component by giving a
suffix of a double underscore and ID (__). For example, to attach
multiple instances of the sound component:
<a-scene>
<a-entity
sound="src: url(sound.mp3)"
sound__beep="src: url(beep.mp3)"
sound__boop="src: url(boop.mp3)"
></a-entity>
</a-scene>From the component lifecycle handler methods, we can differentiate between
instances of the component with this.id. If a component instance is set withfoo__bar, then this.id would be "bar":
AFRAME.registerComponent('foo', {
multiple: true,
update: function () {
console.log('This component instance has the ID', this.id);
}
});If we're doing a setObject3D(), we'll usually want to use this.attrName. If
a component instance is set with foo__bar, then this.attrName would befoo__bar. This gives us a namespace and an ID to set an object3D on the
entity's object3DMap:
AFRAME.registerComponent('foo', {
multiple: true,
update: function () {
// An object3D will be set using `foo__bar` as the key.
this.el.setObject3D(this.attrName, new THREE.Mesh());
}
});`sceneOnly`
The sceneOnly flag indicates if a component can only be applied to the scene
entity. Since sceneOnly is set to false by default, the component can be added
to any entity. For example, any entity could have a geometry component.
But if a component has sceneOnly set to true, then the component can only be
applied to <a-scene>:
AFRAME.registerComponent('foo', {
sceneOnly: true,
// ...
});`events`
The events object allows for conveniently defining event handlers that get
binded and automatically attached and detached at appropriate times during the
component's lifecycle:
- Attached on
.play() - Detached on
.pause()and.remove()
Using events ensures that event handlers properly clean themselves up when
the entity or scene is paused, or the component is detached. If a component's
event handlers are registered manually and not detached properly, the event
handler can still fire even after the component no longer exists.
AFRAME.registerComponent('foo', {
events: {
click: function (evt) {
console.log('This entity was clicked!');
this.el.setAttribute('material', 'color', 'red');
}
}
});`before` / `after`
The before and after properties allow a component to specify when their .tick()
and .tock() methods should be called in relation to other components. This is
useful in cases where the component depends on the result of others. For example, a
component that uses the world position of the users hands would want to run after thehand-tracking-controls component so that the position it sees is up to date.
While both a before and a after constraint can be specified, only one is needed.
A-Frame will automatically determine a suitable order among all registered components.
In case the constraints cause an impossible situation, e.g. when one component would
need to be both before and after another component, a warning will be logged and
the resulting order is undefined.
Here's an example showing how to use before and after:
AFRAME.registerComponent('foo', {
after: ['bar'],
// ...
tick: function() {
console.log('Called second');
}
// ...
});
AFRAME.registerComponent('bar', {
before: ['foo'],
// ...
tick: function() {
console.log('Called first');
}
// ...
});Note that the order is global, meaning in the above example all bar components get
their .tick() method called before any foo component. It does not matter if these
components are on the same entity or not.
Component Prototype Methods
`.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. CallingflushToDOM () will manually serialize the component's data and update the
DOM:
document.querySelector('[geometry]').components.geometry.flushToDOM();Read more about component-to-DOM serialization.
Accessing a Component's Members and Methods
A component's members and methods can be accessed through the entity from the.components object. Look up the component from the entity's map of
components, and we'll have access to the component's internals. Consider this
example component:
AFRAME.registerComponent('foo', {
init: function () {
this.bar = 'baz';
},
qux: function () {
// ...
}
});Let's access the bar member and qux method:
var fooComponent = document.querySelector('[foo]').components.foo;
console.log(fooComponent.bar);
fooComponent.qux();Core Entity (`docs/core/entity.md`)
title: Entity section_title: Core API type: core layout: docs parent_section: docs order: 1 section_order: 3 source_code: src/core/a-entity.js
A-Frame represents an entity via the <a-entity> element. As defined in the
entity-component-system pattern, entities are placeholder objects to
which we plug in components to provide them appearance, behavior, and
functionality.
In A-Frame, entities are inherently attached with the position,
rotation, and scale components.
Example
Consider the entity below. By itself, it has no appearance, behavior, or
functionality. It does nothing:
<a-entity>We can attach components to it to make it render something or do something. To
give it shape and appearance, we can attach the geometry and
material components:
<a-entity geometry="primitive: box" material="color: red">Or to make it emit light, we can further attach the light component:
<a-entity geometry="primitive: box" material="color: red"
light="type: point; intensity: 2.0">Retrieving an Entity
We can simply retrieve an entity using DOM APIs.
<a-entity id="mario"></a-entity>var el = document.querySelector('#mario');Once we have an entity, we have access to its properties and methods detailed
below.
Properties
`components`
<a-entity>.components is an object of components attached to the entity. This
gives us access to the entity's components including each component's data,
state, and methods.
For example, if we wanted to grab an entity's three.js camera object or
material object, we could reach into its components:
var camera = document.querySelector('a-entity[camera]').components.camera.camera;
var material = document.querySelector('a-entity[material]').components.material.material;Or if a component exposes an API, we can call its methods:
document.querySelector('a-entity[sound]').components.sound.pause();`hasLoaded`
Whether the entity has attached and initialized all of its components. Though
the best way to ensure code is run after the entity is ready is to place code
within a component.
`isPlaying`
Whether the entity is active and playing. If we pause the entity , thenisPlaying becomes false.
`object3D`
<a-entity>.object3D is a reference to the entity's three.jsObject3D representation. More specifically, object3D will be aTHREE.Group object that may contain different types of THREE.Object3Ds such
as cameras, meshes, lights, or sounds:
// Gaining access to the internal three.js scene graph.
var groupObject3D = document.querySelector('a-entity').object3D;
console.log(groupObject3D.parent);
console.log(groupObject3D.children);We can access the different types of Object3Ds through object3DMap.
`object3DMap`
An entity's object3DMap is an object that gives access to the different types
of THREE.Object3Ds (e.g., camera, meshes, lights, sounds) that components
have set.
For an entity with a geometry and light components
attached, object3DMap might look like:
{
light: <THREE.Light Object>,
mesh: <THREE.Mesh Object>
}We can manage an entity's set of THREE.Object3Ds by using setObject3D andremoveObject3D.
`sceneEl`
An entity has a reference to its scene element.
var sceneEl = document.querySelector('a-scene');
var entity = sceneEl.querySelector('a-entity');
console.log(entity.sceneEl === sceneEl); // >> true.Methods
`addState (stateName)`
addState will push a state onto the entity. This will emit the stateadded
event, and we can check the state can for existence using .is:
entity.addEventListener('stateadded', function (evt) {
if (evt.detail === 'selected') {
console.log('Entity now selected!');
}
});
entity.addState('selected');
entity.is('selected'); // >> true`destroy ()`
Clean up memory related to the entity such as clearing all components and their data.
`emit (name, detail, bubbles)`
emit emits a custom DOM event on the entity. For example, we can emit an event to
trigger an animation:
// <a-entity animation="property: rotation; to: 0 360 0; startEvents: rotate">
entity.emit('rotate');We can also pass event detail or data as the second argument:
entity.emit('collide', { target: collidingEntity });The event will bubble by default. we can tell it not to bubble by passingfalse for bubble:
entity.emit('sink', null, false);`flushToDOM (recursive)`
flushToDOM will manually serialize an entity's components' data and update the DOM.
Read more about component-to-DOM serialization.
`getAttribute (componentName)`
getAttribute retrieves parsed component data (including mixins and defaults).
// <a-entity geometry="primitive: box; width: 3">
entity.getAttribute('geometry');
// >> {primitive: "box", depth: 2, height: 2, width: 3, ...}
entity.getAttribute('geometry').primitive;
// >> "box"
entity.getAttribute('geometry').height;
// >> 2
entity.getAttribute('position');
// >> {x: 0, y: 0, z: 0}If componentName is not the name of a registered component, getAttribute
will behave as it normally would:
// <a-entity data-position="0 1 1">
entity.getAttribute('data-position');
// >> "0 1 1"`getDOMAttribute (componentName)`
getDOMAttribute retrieves only parsed component data that is explicitly
defined in the DOM or via setAttribute. If componentName is the name of a
registered component, getDOMAttribute will return only the component data
defined in the HTML as a parsed object. getDOMAttribute for components is
the partial form of getAttribute since the returned component data does not
include applied mixins or default values:
Compare the output of the above example of getAttribute:
// <a-entity geometry="primitive: box; width: 3">
entity.getDOMAttribute('geometry');
// >> { primitive: "box", width: 3 }
entity.getDOMAttribute('geometry').primitive;
// >> "box"
entity.getDOMAttribute('geometry').height;
// >> undefined
entity.getDOMAttribute('position');
// >> undefined`getObject3D (type)`
getObject3D looks up a child THREE.Object3D referenced by type on object3DMap.
AFRAME.registerComponent('example-mesh', {
init: function () {
var el = this.el;
el.setObject3D('mesh', new THREE.Mesh());
el.getObject3D('mesh'); // Returns THREE.Mesh that was just created.
}
});`pause ()`
pause() will stop any dynamic behavior as defined by animations and
components. When we pause an entity, it will stop its animations and callComponent.pause() on each of its components. The components decide to
implement what happens on pause, which is often removing event listeners. An
entity will call pause() on its child entities when we pause an entity.
// <a-entity id="spinning-jumping-ball">
entity.pause();For example, the look-controls component on
pause will remove event handlers that listen for input.
`play ()`
play() will start any dynamic behavior as defined by animations and
components. This is automatically called when the DOM attaches an entity. When
an entity play(), the entity calls play() on its child entities.
entity.pause();
entity.play();For example, the sound component on play will begin playing the sound.
`setAttribute (componentName, value, [propertyValue | clobber])`
If componentName is not the name of a registered component or the component is a
single-property component, setAttribute behaves as it normally would:
entity.setAttribute('visible', false);Though if componentName is the name of a registered component, it may handle special
parsing for the value. For example, the position component is a
single-property component, but its property type parser allows it to take an
object:
entity.setAttribute('position', { x: 1, y: 2, z: 3 });Updating Multi-Property Component Data
To update component data for a multi-property component, we can pass the name
of a registered component as the componentName, and pass an object of
properties as the value. A string is also acceptable (e.g., type: spot; distance: 30), but objects will save A-Frame some work in parsing:
// Only the properties passed in the object will be overwritten.
entity.setAttribute('light', {
type: 'spot',
distance: 30,
intensity: 2.0
});Or to update individual properties for a multi-property component, we can pass
the name of registered component as the componentName, a property name as the second
argument, and the property value to set as the third argument:
// All previous properties for the material component (besides the color) will be unaffected.
entity.setAttribute('material', 'color', 'crimson');Note that array property types behave uniquely:
- Arrays are mutable. They are assigned by reference so changes to arrays will
be visible by the component. - Updates to array type properties will not trigger the component's
updatemethod
nor emit events.
Putting Multi-Property Component Data
If true is passed as the third argument to .setAttribute, then
non-specified properties will be reset and clobbered:
// All previous properties for the light component will be removed and overwritten.
entity.setAttribute('light', {
type: 'spot',
distance: 30,
intensity: 2.0
}, true);`setObject3D (type, obj)`
setObject3D will register the passed obj, a THREE.Object3D, as type
under the entity's object3DMap. A-Frame adds obj as a child of the entity's
root object3D. An entity will emit the object3dset event with type event
detail when setObject3D is called.
AFRAME.registerComponent('example-orthogonal-camera', {
update: function () {
this.el.setObject3D('camera', new THREE.OrthogonalCamera());
}
});`removeAttribute (componentName, propertyName)`
If componentName is the name of a registered component, along with removing the
attribute from the DOM, removeAttribute will also detach the component from
the entity, invoking the component's remove lifecycle method.
entity.removeAttribute('geometry'); // Detach the geometry component.
entity.removeAttribute('sound'); // Detach the sound component.If propertyName is given, removeAttribute will reset the property value of
that property specified by propertyName to the property's default value:
entity.setAttribute('material', 'color', 'blue'); // The color is blue.
entity.removeAttribute('material', 'color'); // Reset the color to the default value, white.`removeObject3D (type)`
removeObject3D removes the object specified by type from the entity'sTHREE.Group and thus from the scene. This will update the entity'sobject3DMap, setting the value of the type key to null. This is generally
called from a component, often within the remove handler:
AFRAME.registerComponent('example-light', {
update: function () {
this.el.setObject3D('light', new THREE.Light());
// Light is now part of the scene.
// object3DMap.light is now a THREE.Light() object.
},
remove: function () {
this.el.removeObject3D('light');
// Light is now removed from the scene.
// object3DMap.light is now null.
}
});`removeState (stateName)`
removeState will pop a state from the entity. This will emit thestateremoved event, and we can check the state its removal using .is:
entity.addEventListener('stateremoved', function (evt) {
if (evt.detail.state === 'selected') {
console.log('Entity no longer selected.');
}
});
entity.addState('selected');
entity.is('selected'); // >> true
entity.removeState('selected');
entity.is('selected'); // >> falseEvents
| Event Name | Description |
|---|---|
| child-attached | A child entity was attached to the entity. |
| child-detached | A child entity was detached from the entity. |
| componentchanged | One of the entity's components was modified. This event is throttled. Do not use this for reading position and rotation changes, rather use a tick handler. |
| componentinitialized | One of the entity's components was initialized. |
| componentremoved | One of the entity's components was removed. |
| loaded | The entity has attached and initialized its components. |
| object3dset | THREE.Object3D was set on entity using setObject3D(name). Event detail will contain name used to set on the object3DMap. |
| pause | The entity is now inactive and paused in terms of dynamic behavior. |
| play | The entity is now active and playing in terms of dynamic behavior. |
| stateadded | The entity received a new state. |
| stateremoved | The entity no longer has a certain state. |
| schemachanged | The schema of a component was changed. |
Event Detail
Below is what the event detail contains for each event:
| Event Name | Property | Description |
|---|---|---|
| child-attached | el | Reference to the attached child element. |
| componentchanged | name | Name of component that had its data modified. |
| id | ID of component that had its data modified. | |
| componentinitialized | name | Name of component that was initialized. |
| id | ID of component that had its data modified. | |
| data | Component data. | |
| componentremoved | name | Name of component that was removed. |
| id | ID of component that was removed. | |
| stateadded | N/A | The state that was attached (string). |
| stateremoved | N/A | The state that was detached (string). |
| schemachanged | component | Name of component that had it's schema changed. |
Listening for Component Changes
We can use the componentchanged event to listen for changes to the entity:
entity.addEventListener('componentchanged', function (evt) {
if (evt.detail.name === 'position') {
console.log('Entity has moved to', evt.target.getAttribute('position'), '!');
}
});Listening for Child Elements Being Attached and Detached
We can use the child-attached and child-detached events to listen for when
the scene attaches or detaches an entity:
entity.addEventListener('child-attached', function (evt) {
if (evt.detail.el.tagName.toLowerCase() === 'a-box') {
console.log('a box element has been attached');
};
});Core Globals (`docs/core/globals.md`)
title: Globals type: core layout: docs parent_section: core order: 10 source_code: src/index.js
A-Frame exposes its public interface through the window.AFRAME browser
global. This same interface is also exposed when you import aframe (import AFRAME from 'aframe').
`AFRAME` Properties
| Property | Description |
|---|---|
| AComponent | Component prototype. |
| AEntity | Entity prototype. |
| ANode | Base node prototype that A-Frame elements inherit from. |
| AScene | Scene prototype. |
| components | Object of registered components. |
| geometries | Object of registered geometries . |
| primitives.primitives | Object of registered primitives. |
| registerComponent | Function to register a component. |
| registerGeometry | Function to register a geometry. |
| registerPrimitive | Function to register a primitive like registering an A-Frame elements similar to <a-box>. |
| registerShader | Function to register a material or shader. |
| schema | Schema-related utilities. |
| shaders | Object of registered shaders. |
| systems | Object of registered systems. |
| THREE | Global three.js object. |
| utils | A-Frame utility modules. |
| version | Version of A-Frame build. |
`window` Properties
| Property | Description |
|---|---|
| AFRAME | The object described above. |
Requiring `AFRAME` in a Node.js Environment
It is possible to run A-Frame in Node.js to get access to its globals. The only catch is we need to supply a browser window mock since Node.js lacks a window object. You can do that with jsdom-global, and you also need to mock customElements.
const cleanup = require('jsdom-global')();
global.customElements = { define: function () {} };
const aframe = require('aframe');
console.log(aframe.version);
cleanup();You can't use jsdom to run tests with aframe components because customElements api is missing. A-Frame is using karma to open a real browser to run the tests.
Core Mixins (`docs/core/mixins.md`)
title: Mixins
type: core
layout: docs
parent_section: core
order: 7
source_code: src/core/a-mixin.js
examples:
- title: Animated Lights
src: https://aframe.io/aframe/examples/showcase/dynamic-lights/
Mixins provide a way to compose and reuse commonly-used sets of component
properties. They are defined using the <a-mixin> element and are placed in<a-assets>. Mixins should be set with an id, and when an entity
sets that id as its mixin attribute, the entity will absorb all of the
mixin's attributes.
<a-scene>
<a-assets>
<a-mixin id="red" material="color: red"></a-mixin>
<a-mixin id="blue" material="color: blue"></a-mixin>
<a-mixin id="cube" geometry="primitive: box"></a-mixin>
</a-assets>
<a-entity mixin="red cube"></a-entity>
<a-entity mixin="blue cube"></a-entity>
</a-scene>The entity with red cube will take the properties from the red mixin and
the cube mixin in that order. Likewise with the blue cube. Conceptually,
the entities above expand to:
<a-entity material="color: red" geometry="primitive: box"></a-entity>
<a-entity material="color: blue" geometry="primitive: box"></a-entity>Merging Component Properties
Properties of a multi-property component will merge if defined by multiple mixins and/or the entity. For example:
<a-scene>
<a-assets>
<a-mixin id="box" geometry="primitive: box"></a-mixin>
<a-mixin id="tall" geometry="height: 10"></a-mixin>
<a-mixin id="wide" geometry="width: 10"></a-mixin>
</a-assets>
<a-entity mixin="wide tall box" geometry="depth: 2"></a-entity>
</a-scene>All of the geometry component properties will merge since they are included as mixins and defined on the entity. The entity would then be equivalent to:
<a-entity geometry="primitive: box; height: 10; depth: 2; width: 10"></a-entity>Order and Precedence
When an entity includes multiple mixins that define the same component
properties, the right-most mixin takes precedence. In the example below, the
entity includes both red and blue mixins, and since the blue mixin is
included last, the final color of the cube will be blue.
<a-scene>
<a-assets>
<a-mixin id="red" material="color: red"></a-mixin>
<a-mixin id="blue" material="color: blue"></a-mixin>
<a-mixin id="cube" geometry="primitive: box"></a-mixin>
</a-assets>
<a-entity mixin="red blue cube"></a-entity>
</a-scene>If an entity itself defines a property that is already defined by a mixin, the entity's definition takes precedence. In the example below, the entity includes both red and blue mixins and also defines a green color. Since the entity directly defines its own color, the final color of the cube will be green.
<a-scene>
<a-assets>
<a-mixin id="red" material="color: red"></a-mixin>
<a-mixin id="blue" material="color: blue"></a-mixin>
<a-mixin id="cube" geometry="primitive: box"></a-mixin>
</a-assets>
<a-entity mixin="red blue cube" material="color: green"></a-entity>
</a-scene>Core Scene (`docs/core/scene.md`)
title: Scene type: core layout: docs parent_section: core order: 5 source_code: src/core/scene/a-scene.js examples: []
A scene is represented by the <a-scene> element. The scene is the global root
object, and all entities are contained within the scene.
The scene inherits from the Entity class so it inherits all of its
properties, its methods, the ability to attach components, and the behavior to
wait for all of its child nodes (e.g., <a-assets> and <a-entity>) to load
before kicking off the render loop.
<a-scene> handles all of the three.js and WebVR/WebXR boilerplate for us:
- Set up canvas, renderer, render loop
- Default camera and lights
- Set up webvr-polyfill, VREffect
- Add UI to Enter VR that calls WebVR API
- Configure WebXR devices through the
webxrsystem
Example
<a-scene>
<a-assets>
<img id="texture" src="texture.png">
</a-assets>
<a-box src="#texture"></a-box>
</a-scene>Properties
| Name | Description |
|---|---|
| behaviors | Array of components with tick methods that will be run on every frame. |
| camera | Active three.js camera. |
| canvas | Reference to the canvas element. |
| isMobile | Whether or not environment is detected to be mobile. |
| object3D | THREE.Scene object. |
| renderer | Active THREE.WebGLRenderer. |
| renderStarted | Whether scene is rendering. |
| effect | Renderer for VR created by passing active renderer into THREE.VREffect. |
| systems | Instantiated systems. |
| time | Global uptime of scene in seconds. |
States
| Name | Description |
|---|---|
| vr-mode | Added and removed when entering and exiting VR, respectively. Check with sceneEl.is('vr-mode'). |
Methods
| Name | Description |
|---|---|
| enterVR | Switch to stereo render and push content to the headset. Needs to be called within a user-generated event handler like click. the first time a page enters VR. |
| exitVR | Switch to mono renderer and stops presenting content on the headset. |
Events
| Name | Description |
|---|---|
| enter-vr | User has entered VR and headset started presenting content. |
| exit-vr | User has exited VR and headset stopped presenting content. |
| loaded | All nodes have loaded. |
| renderstart | Render loop has started. |
Scene Components
Components can be attached to the scene as well as entities:
<a-scene fog stats>A-Frame ships with a few components to configure the scene:
- embedded - Remove fullscreen styles from the canvas.
- fog - Add fog.
- keyboard-shortcuts - Toggle keyboard shortcuts.
- inspector - Inject the A-Frame Inspector.
- stats - Toggle performance stats.
- xr-mode-ui - Toggle UI for entering and exiting VR.
Running Content Scripts on the Scene
The recommended way is to write a component, and attach it to the scene element. The scene and its children will be initialized before this component.
AFRAME.registerComponent('do-something', {
init: function () {
var sceneEl = this.el;
}
});<a-scene do-something></a-scene>If for some particular reason you prefer not to write a dedicated component you need to wait for the scene to finish initializing and attaching:
var scene = document.querySelector('a-scene');
if (scene.hasLoaded) {
run();
} else {
scene.addEventListener('loaded', run);
}
function run () {
var entity = scene.querySelector('a-entity');
entity.setAttribute('material', 'color', 'red');
}Core Systems (`docs/core/systems.md`)
title: System
type: core
layout: docs
parent_section: core
order: 4
source_code: src/core/system.js
examples:
- title: System providing envMaps
src: https://glitch.com/~aframe-refraction-system
A system, of the entity-component-system pattern, provides global scope,
services, and management to classes of components. It provides public APIs
(methods and properties) for classes of components. A system can be accessed
through the scene element, and can help components interface with the global
scene.
For example, the camera system manages all entities with the camera
component, controlling which camera is the active camera.
Registering a System
A system is registered similarly to a component.
If the system name matches a component name, then the component will have a
reference to the system as this.system:
AFRAME.registerSystem('my-component', {
schema: {}, // System schema. Parses into `this.data`.
init: function () {
// Called on scene initialization.
},
// Other handlers and methods.
});
AFRAME.registerComponent('my-component', {
init: function () {
console.log(this.system);
}
});Properties
| Property | Description |
|---|---|
| data | Data provided by the schema available across handlers and methods. |
| el | Reference to <a-scene>. |
| schema | Behaves the same as component schemas. Parses to data. |
Methods
A system, like a component, defines lifecycle handlers. It can also define
methods intended to be public API.
| Method | Description |
|---|---|
| init | Called once when the system is initialized. Used to initialize. |
| pause | Called when the scene pauses. Used to stop dynamic behavior. |
| play | Called when the scene starts or resumes. Used to start dynamic behavior. |
| tick | If defined, will be called on every tick of the scene's render loop. |
Accessing a System
An instantiated system can be accessed through the scene:
document.querySelector('a-scene').systems[systemName];Registered system prototypes can be accessed through AFRAME.systems.
Patterns
Separation of Logic and Data
Systems can help separate logic and behavior from data if desired. We let
systems handle the heavy lifting, and components only worry about managing its
data through its lifecycle methods:
AFRAME.registerSystem('my-component', {
createComplexObject: function (data) {
// Do calculations and stuff with data.
return new ComplexObject(data);
}
});
AFRAME.registerComponent('my-component', {
init: function () {
this.myObject = null;
},
update: function () {
// Do stuff with `this.data`.
this.myObject = this.system.createComplexObject(this.data);
}
});Gathering All Components of a System
There is no strict API for defining how systems manage components. A common
pattern is to have components subscribe themselves to the system. The system
then has references to all of its components:
AFRAME.registerSystem('my-component', {
init: function () {
this.entities = [];
},
registerMe: function (el) {
this.entities.push(el);
},
unregisterMe: function (el) {
var index = this.entities.indexOf(el);
this.entities.splice(index, 1);
}
});
AFRAME.registerComponent('my-component', {
init: function () {
this.system.registerMe(this.el);
},
remove: function () {
this.system.unregisterMe(this.el);
}
});Core Utils (`docs/core/utils.md`)
title: Utils type: core layout: docs parent_section: core order: 11 source_code: src/utils/index.js
A-Frame's utility modules are public through AFRAME.utils.
`AFRAME.utils.coordinates`
Module for handling vec3 and vec4 types.
`.isCoordinates (value)`
Tests whether a string is a vec3 or vec4.
AFRAME.utils.coordinates.isCoordinates('1 2 3')
// >> true
AFRAME.utils.coordinates.isCoordinates('1 2 3 4')
// >> true`.parse (value)`
Parses an "x y z" string to an {x, y, z} vec3 object. Or parses an "x y z w" string to an {x, y, z, w} vec4 object.
AFRAME.utils.coordinates.parse('1 2 -3')
// >> {x: 1, y: 2, z: -3}`.stringify (data)`
Stringifies an {x, y, z} vec3 object to an "x y z" string.Or Stringifies an {x, y, z, w} vec4 object to an "x y z w" string.
AFRAME.utils.coordinates.stringify({x: 1, y: 2, z: -3})
// >> "1 2 -3"
AFRAME.utils.coordinates.stringify({x: 1, y: 2, z: -3, w:4})
// >> "1 2 -3 4"`AFRAME.utils.entity`
`.getComponentProperty(entity, componentName, delimiter='.')`
Performs like Entity.getAttribute, but with support for
return an individual property for a multi-property component. componentName
is a string that can either be a component name, or a component name delimited
with a property name.
// <a-entity id="box" geometry="primitive: box"></a-entity>
var entity = document.querySelector('#box');
AFRAME.utils.entity.getComponentProperty(entity, 'geometry.primitive');
AFRAME.utils.entity.getComponentProperty(entity, 'geometry|primitive', '|');
// >> 'box'
AFRAME.utils.entity.getComponentProperty(entity, 'geometry');
// >> {primitive: 'box', width: 1, ...}This is useful for components that need a way to reference a property of a
multi-property component.
`.setComponentProperty (entity, componentName, value, delimiter)`
Performs like Entity.setAttribute, but with support for setting an
individual property for a multi-property component. componentName is a string
that can either be a component name, or a component name delimited with a
property name.
// <a-entity id="box" geometry="primitive: box"></a-entity>
var entity = document.querySelector('#box');
AFRAME.utils.entity.setComponentProperty(entity, 'geometry.width', 1);
AFRAME.utils.entity.setComponentProperty(entity, 'geometry|height', 2, '|');
AFRAME.utils.entity.setComponentProperty(entity, 'geometry', {depth: 3});AFRAME.utils.styleParser
`.parse (value)`
Parses a CSS style-like string to an object.
AFRAME.utils.styleParser.parse('attribute: color; dur: 5000;')
// >> {"attribute": "color", "dur": "5000"}`.stringify (data)`
Stringifies an object to a CSS style-like string.
AFRAME.utils.styleParser.stringify({height: 10, width: 5})
// >> "height: 10; width: 5"Object Utils
`AFRAME.utils.deepEqual (a, b)`
Checks if two objects have the same attributes and values, including nested objects.
deepEqual({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}})
// >> true`AFRAME.utils.diff (a, b)`
Returns difference between two objects. The returned object's set of keys denote which values were not equal, and the set of values are b's values.
diff({a: 1, b: 2, c: 3}, {b: 2, c: 4})
// >> {"a": undefined, "c": 4}`AFRAME.utils.extend(target, source, [source, ...])`
`AFRAME.utils.extendDeep (target, source, [source, ...])`
`AFRAME.utils.device`
`AFRAME.utils.device.checkHeadsetConnected ()`
Checks if a VR headset is connected by looking for browser support of
immersive-vr or immersive-ar WebXR session. Returns a boolean.
`AFRAME.utils.device.isMobile ()`
Checks if device is a smartphone. Returns a boolean.
`AFRAME.utils.device.isTablet ()`
Checks if device is a tablet. Returns a boolean.
`AFRAME.utils.device.isMobileVR ()`
Checks if device is a standalone headset. Returns a boolean.
Function Utils
`AFRAME.utils.throttle (function, minimumInterval [, optionalContext])`
Returns a throttled function that is called at most once everyminimumInterval milliseconds. A context such as this can be provided to
handle function binding for convenience. The same as lodash'sthrottle.
AFRAME.registerComponent('foo', {
init: function () {
// Set up throttling.
this.throttledFunction = AFRAME.utils.throttle(this.everySecond, 1000, this);
},
everySecond: function () {
// Called every second.
console.log("A second passed.");
},
tick: function (t, dt) {
this.throttledFunction(); // Called once a second.
console.log("A frame passed."); // Called every frame.
},
});`AFRAME.utils.throttleTick (function (t, dt) {...}, minimumInterval [, optionalContext])`
Returns a throttled function that is called at most once everyminimumInterval milliseconds. A context such as this can be provided to
handle function binding for convenience.
This variant of .throttle() is slightly more performant and tailored fortick handlers as it uses the t and dt timestamps passed by the global
render loop.
AFRAME.registerComponent('foo', {
init: function () {
// Set up the tick throttling.
this.tick = AFRAME.utils.throttleTick(this.tick, 500, this);
},
/**
* Tick function that will be wrapped to be throttled.
*/
tick: function (t, dt) {}
});`AFRAME.utils.throttleLeadingAndTrailing (function, minimumInterval [, optionalContext])`
Returns a throttled function that is called at most once every minimumInterval milliseconds, but ensures that the very last call of a burst gets deferred until the end of the interval. This is useful when an event is used to trigger synchronization of state, and there is a need to converge to the correct final state following a burst of events.
Example use cases:
- synchronizing state based on the componentchanged event
- following a mouse pointer using the mousemove event
- integrating with THREE.TransformControls, via the objectChange event.
A context such as this can be provided to handle function binding for convenience.
The same as lodash'sthrottle, with leading and trailing options both set to true
Miscellaneous
`AFRAME.utils.getUrlParameter (name)`
Returns the value of a URL parameter as a string, otherwise returns an empty
string.
AFRAME.utils.getUrlParameter('testing');
// If visiting the current page with ?testing=aframe, this will log 'aframe'.Guides Building A 360 Image Gallery (`docs/guides/building-a-360-image-gallery.md`)
title: Building a 360° Image Gallery
type: guides
layout: docs
parent_section: guides
order: 3
examples:
- title: 360° Image Gallery
src: https://github.com/aframevr/aframe/tree/master/examples/docs/360-gallery/index.html
Let's build an interactive gaze-based 360° image gallery. There will be
three panels which the user can click on. Once clicked, the background will
fade and swap the 360° images.
This guide will practice three concepts related to entity-component:
- Using the standard components that come with A-Frame.
- Using community components from the ecosystem.
- Writing custom components to accomplish whatever we want.
Not to say 360° images are at all a focus use case of A-Frame, but it makes
for an easy example that has a lot of demand as an early use case on the Web.
Skeleton
This is the starting point for our scene.
<a-scene>
<a-assets>
<audio id="click-sound" src="https://cdn.aframe.io/360-image-gallery-boilerplate/audio/click.ogg"></audio>
<img id="city" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/city.jpg">
<img id="city-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-city.jpg">
<img id="cubes" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/cubes.jpg">
<img id="cubes-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-cubes.jpg">
<img id="sechelt" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">
<img id="sechelt-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-sechelt.jpg">
</a-assets>
<a-sky id="image-360" radius="10" src="#city"></a-sky>
<a-entity class="link"></a-entity>
<a-camera>
<a-cursor
id="cursor"
animation__click="property: scale; from: 0.1 0.1 0.1; to: 1 1 1; easing: easeInCubic; dur: 150; startEvents: click"
animation__clickreset="property: scale; to: 0.1 0.1 0.1; dur: 1; startEvents: animationcomplete__click"
animation__fusing="property: scale; from: 1 1 1; to: 0.1 0.1 0.1; easing: easeInCubic; dur: 150; startEvents: fusing"></a-cursor>
</a-camera>
</a-scene>We have predefined:
- Several images to preload in the Asset Management System within
<a-assets>. Note not all our assets must be predefined or preloaded. - Our 360° image placeholder with
<a-sky>. - A cursor with visual feedback using event-driven
animations, fixed to the camera.
Using Standard Components
Standard components are components that ship with A-Frame, like any standard
library. We'll go over how to attach these components to entities and configure
them from HTML.
We want to build a textured plane to act as a link that when clicked, will
change the 360° image. We start with an empty entity. Without any
components, any empty entity does nothing and renders nothing:
<a-entity class="link"></a-entity>To give our entity shape, we can attach the geometry component,
configured to a plane shape. We specify the component data using a syntax that
resembles that of inline CSS styles:
<a-entity
class="link"
geometry="primitive: plane; height: 1; width: 1"></a-entity>Then to give our entity appearance, we can attach the material
component. We set shader to flat so the image isn't affected
negatively by lighting. And we set src to #cubes-thumb, a selector to one
of the images preloaded in the Asset Management System. Alternatively,
we could pass a URL for the image:
<a-entity class="link"
geometry="primitive: plane; height: 1; width: 1"
material="shader: flat; src: #cubes-thumb"></a-entity>We can continue adding features to our entity by plugging in more components.
Let's attach one more standard component, the sound component. We want
to make it such that when we click (via gazing) on the link, it plays a click
sound. The syntax is the same as before, but instead we are now using the sound
component's properties. We set on to click so the sound is played on click.
And we set src to #click-sound, a selector to our <audio> element.
<a-entity class="link"
geometry="primitive: plane; height: 1; width: 1"
material="shader: flat; src: #cubes-thumb"
sound="on: click; src: #click-sound"></a-entity>Now we have a textured plane that plays a click sound when clicked.
Using Community Components
A-Frame comes with a small core of standard components, but lot of magic comes
from the large number of open source community components in the A-Frame
ecosystem. We can find community components from places such as npm. We
can drop them into our scene and use them straight in our HTML. Components are
capable of doing anything and abstract hundreds of lines of code into a single
component that can be plugged in via an HTML attribute, like physics!
We'll go through using four community components:
Community components are generally available through both GitHub and published
on npm. An easy way to include components is to use the unpkg.com
CDN, which lets us include components hosted on npm as a
script tag, even with support for specifying fuzzy versions. We usually just
need to know the component's npm package name and the path:
<html>
<head>
<title>360° Image Browser</title>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-template-component.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-layout-component.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-proxy-event-component.min.jss"></script>
</head>
<body>
<a-scene>
</a-scene>
</body>
</html>`template` Component to Create the Links
Currently, we have one link. We want to create three of them, one for each of
our 360° images. We want to be able to reuse the HTML definition for all of
them.
One solution is the template component, which integrates templating
engines into A-Frame at runtime. This lets us do things such as encapsulate
groups of entities, passing data to generate entities, or iteration. Since we
want to turn one link into three, without copy-and-pasting HTML, we can use the
template component.
Ideally, we would do this at build time (e.g., with the Super Nunjucks Webpack Loader),
instead of wasting time doing it at runtime. But for simplicity for this
tutorial to demonstrate components, we will use the template component. In
practice, we'd want to do it with a module bundler such as Webpack.
If we read the template component's documentation, we see one way
to define a template is via a script tag in <head>. Let's make our link a
template and give it a name using an id:
<head>
<script id="link" type="text/html">
<a-entity class="link"
geometry="primitive: plane; height: 1; width: 1"
material="shader: flat; src: #cubes-thumb"
sound="on: click; src: #click-sound"></a-entity>
</script>
</head>Then we can use the template to create multiple planes without much work:
<a-entity template="src: #link"></a-entity>
<a-entity template="src: #link"></a-entity>
<a-entity template="src: #link"></a-entity>But then they'll all be displaying the same image texture and look the same.
Here is where we'll need a template engine with variable substitution. The
template component comes with simple ES6 string
interpolation (i.e., ${var} format).
To allow each instance of the template to be customizable, we define a${thumb} variable in the template, which we can pass using data
attributes:
<a-assets>
<script id="link" type="text/html">
<a-entity class="link"
geometry="primitive: plane; height: 1; width: 1"
material="shader: flat; src: ${thumb}"
sound="on: click; src: #click-sound"></a-entity>
</script>
</a-assets>
<a-entity template="src: #link" data-thumb="#city-thumb"></a-entity>
<a-entity template="src: #link" data-thumb="#cubes-thumb"></a-entity>
<a-entity template="src: #link" data-thumb="#sechelt-thumb"></a-entity>The template component has allowed us to not have to repeat a lot of HTML,
keeping our scene very readable.
`layout` Component to Lay Out Links
Because the default position of an entity is 0 0 0, the entities will
overlap. While we could manually position each link, we could instead use the
layout component to do it for us. The layout component will
automatically position its children to the specified layout.
We create a wrapper entity around our links and attach the layout component
using the line layout:
<a-entity id="links" layout="type: line; margin: 1.5" position="-1.5 -1 -4">
<a-entity template="src: #link" data-thumb="#city-thumb"></a-entity>
<a-entity template="src: #link" data-thumb="#cubes-thumb"></a-entity>
<a-entity template="src: #link" data-thumb="#sechelt-thumb"></a-entity>
</a-entity>Now our links are no longer overlapping without us having to calculate and
fiddle with positions. The layout component supports other layouts including
grid, circle, and dodecahedron. The layout component is fairly simple, but we
can imagine in the future, they can get more and more powerful while retaining
the same simplicity of use.
`event-set` Component for Visual Feedback on Hover
Lastly, we'll add some visual feedback to our links. We want them to scale up
and scale back when they are hovered or clicked. This involves writing an event
listener to do setAttributes on the scale component in response to
cursor events. This is a fairly common pattern so there is an
event-set component that does setAttribute in response to events.
Let's attach event listeners on our links to scale them up when they are gazed
over, scale them down as they are being clicked, and scale them back when they
are no longer gazed upon. We can specify event names either the _event
property or via the __ as shown below. The rest of the properties define
the setAttribute calls. Notice that the event-set component can have
multiple instances:
<script id="link" type="text/html">
<a-entity class="link"
geometry="primitive: plane; height: 1; width: 1"
material="shader: flat; src: ${thumb}"
sound="on: click; src: #click-sound"
event-set__mouseenter="scale: 1.2 1.2 1"
event-set__mouseleave="scale: 1 1 1"
event-set__click="_target: #image-360; _delay: 300; material.src: ${src}"></a-entity>
</script>Remember to add data-src attributes to the link entities to load the full image on click:
<a-entity template="src: #link" data-src="#city" data-thumb="#city-thumb"></a-entity>
<a-entity template="src: #link" data-src="#cubes" data-thumb="#cubes-thumb"></a-entity>
<a-entity template="src: #link" data-src="#sechelt" data-thumb="#sechelt-thumb"></a-entity>Next, we want to actually set the new background image. We'll add a nice fade-to-black effect.
The last event-set__click is more complex in that it sets a property on
another entity (our background noted as ID #image-360) with a delay of 300,
setting the texture with material.src. The delay of 300 will allow for the
fade-to-black animation to run before setting the texture.
`proxy-event` Component to Change the Background
Next, we want to wire up the click on the link to actually changing the
background. We can use proxy-set to pass an event from one entity to
another. It's a convenient way for telling the background that one of the links
was clicked in order to begin the animation:
<a-entity
class="link"
proxy-event="event: click; to: #image-360; as: fade"></a-entity>When the link is clicked, it will emit the event also on our background (IDed
as #image-360), renaming the event from click to fade. Now let's handle this event
to begin the animation:
<a-sky
id="image-360" radius="10" src="#city"
animation__fade="property: components.material.material.color; type: color; from: #FFF; to: #000; dur: 300; startEvents: fade"
animation__fadeback="property: components.material.material.color; type: color; from: #000; to: #FFF; dur: 300; startEvents: animationcomplete__fade"></a-sky>We set two animations, one setting the color to fade to black, and one setting
the color to fade back to normal. The animation__fade sets to black,
listening to the fade event that we "proxied" earlier.
The animation__fadeback is interesting in that we start it once theanimation__fade completes by listening to the animationcomplete__fade event
that is emitted by animation component when an animation finishes. We
effectively chained these animations!
Wielding components, we were able to do a lot in a few dozen lines of HTML,
working on VR across most headsets and browsers. Though the ecosystem has a lot
to offer for common needs, non-trivial VR applications will require us to write
application-specific components. That is covered in Writing a Component and
hopefully in later guides.
Guides Building A Basic Scene (`docs/guides/building-a-basic-scene.md`)
title: Building a Basic Scene
section_title: Guides
type: guides
layout: docs
parent_section: docs
order: 1
section_order: 2
nav_expand_sections: true
image:
src: https://user-images.githubusercontent.com/674727/29746371-b7906dcc-8a8c-11e7-8ef4-3f7e4d224ce7.jpg
examples:
- title: Basic Guide with Environment
src: https://github.com/aframevr/aframe/tree/master/examples/docs/basic-scene/index.html
Let's start by building a basic A-Frame scene. For this, we will need a basic
understanding of HTML. We will learn how to:
- Add 3D entities (i.e., objects) with primitives
- Transform entities in 3D space with position, rotation, scale
- Add an environment
- Add textures
- Add basic interactivity using animations and events
- Add text
Remix the Basic Guide example on Glitch.
Starting with HTML
We start out with a minimal HTML structure:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
</a-scene>
</body>
</html>We include A-Frame as a script tag in the <head>, pointing to the A-Frame
build hosted on a CDN. This has to be included before <a-scene> because
A-Frame registers custom HTML elements which must be defined before <a-scene>
is attached or else <a-scene> will do nothing.
Next, we include <a-scene> in the ``. <a-scene> will contain every
entity in our scene. <a-scene> handles all of the setup that is required for
3D: setting up WebGL, the canvas, camera, lights, renderer, render loop as well
as out of the box WebXR support on headsets and WebXR enabled browser on smartphones.<a-scene> alone takes a lot of load off of us!
Adding an Entity
Within our <a-scene>, we attach 3D entities using one of A-Frame's standard
primitives <a-box>. We can use <a-box> just like a normal HTML element,
defining the tag and using HTML attributes to customize it. Some other examples
of primitives that come with A-Frame include <a-cylinder>, <a-plane>, or<a-sphere>.
Here we define the color <a-box>, see <a-box>'s documentation for
the more attributes (e.g., width, height, depth).

Image by Ruben Mueller from vrjump.de
<a-scene>
<a-box color="red"></a-box>
</a-scene>As a side note, primitives are A-Frame's easy-to-use HTML elements that wrap
the underlying entity-component assembly. They can be convenient, but
underneath <a-box> is <a-entity> with the geometry and
material components:
<a-entity id="box" geometry="primitive: box" material="color: red"></a-entity>However, because the default camera and the box are positioned at the default
position at the 0 0 0 origin, we won't be able to see the box unless we move
it. We can do this by using the position component to transform the
box in 3D space.
Transforming an Entity in 3D
Let's first go over 3D space. A-Frame uses a right-handed coordinate system.
With the default camera direction: positive X-axis extends right, positive
Y-axis extends up, and the positive Z-axis extends out of the screen
towards us:

Image from what-when-how.com
A-Frame's distance unit is in meters because the WebVR
API returns pose data in meters. When designing a scene for VR, it is
important to consider the real world scale of the entities we create. A box withheight="10" may look normal on our computer screens, but in VR the box will
appear massive.
A-Frame's rotational unit in A-Frame is in degrees, although it will get
internally converted to radians when passing to three.js. To determine the
positive direction of rotation, use the right-hand rule. Point our thumbs
down the direction of a positive axis, and the direction which our fingers curl
around the positive direction of rotation.
To translate, rotate, and scale the box, we can change the
position, rotation, and scale components.
Let's first apply the rotation and scale components:
<a-scene>
<a-box color="red" rotation="0 45 45" scale="2 2 2"></a-box>
</a-scene>This will rotate our box at an angle and double its size.
Parent and Child Transforms
A-Frame HTML represent a 3D scene graph. In a scene graph, entities can have
a single parent and multiple children. Child entities inherit transformations
(i.e., position, rotation, and scale) from their parent.
For example, we could have a sphere as a child of a box:
<a-scene>
<a-box position="0 2 0" rotation="0 45 45" scale="2 4 2">
<a-sphere position="1 0 3"></a-sphere>
</a-box>
</a-scene>If we calculate the sphere's world position, it would be 1 2 3, achieved by
composing the sphere's parent position with its own position. Similarly, for
rotation and scale, the sphere would inherit the box's rotation and scale. The
sphere too would be rotated and stretched just as like its parent box. If the
box were to change its position, rotation, or scale, it would immediately apply
to and affect the sphere.
If we were to add a cylinder as a child to our sphere, the cylinder's transform
would be affected by both the sphere's and box's transforms. Under the hood
in three.js, this is done by multiplying transformation matrices together.
Fortunately, we don't have to think about that!
Placing our Box in Front of the Camera
Now let's get back to making our box visible to the camera from the start. We
can move the box back 5 meters on the negative Z-axis with the position
component. We also have to move it up 2 meters on the positive Y-axis so the
box doesn't intersect with the ground since we scaled the box and scaling happens
from the center:
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
</a-scene>Now we see our box!

Default Controls
For flat displays (i.e., laptop, desktop), the default control scheme lets us
look around by click-dragging the mouse and move around with the WASD or
arrow keys. On mobile, we can pan the phone around to rotate the camera.
Although A-Frame is tailored for WebVR, this default control scheme allows
people to view scenes without a headset.

Upon entering VR by clicking the goggles icon with a VR headset
connected (e.g., Oculus Rift, HTC Vive), we can experience the scene in
immersive VR. If room-scale is available, we can physically walk around the
scene!
Adding an Environment
A-Frame allows developers to create and share reusable components for others to
easily use. The environment component procedurally
generates a variety of entire environments for us with a single line of HTML.
The environment component is a great and easy way to visually bootstrap our VR
application, providing over a dozen environments with numerous parameters:
First, include the environment component using a script tag after A-Frame:
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-environment-component.min.js"></script>
</head>Then within the scene, add an entity with the environment component attached.
We can specify a preset (e.g., forest) with along many other parameters
(e.g., 200 trees):
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-entity environment="preset: forest; dressingAmount: 500"></a-entity>
</a-scene>Applying an Image Texture
Make sure you're serving your HTML using a local server
for textures to load properly.
Due to an issue with imgur.com, view the page using http://localhost, rather than http://127.0.0.1
We can apply an image texture to the box with an image, video, or <canvas>
using the src attribute, just like we would with a normal `` element.
We also should remove the color="red" that we set so that the color doesn't
get blended in with the texture. The default material color is white, so
removing the color attribute is good enough.
<a-scene>
<a-box src="https://i.imgur.com/mYmmbrp.jpg" position="0 2 -5" rotation="0 45 45"
scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>Now we'll see our box with an image texture pulled from online:

Using the Asset Management System
However, we recommend using the asset management system for performance. The
asset management system makes it easier for the browser to cache assets (e.g.,
images, videos, models) and A-Frame will make sure all of the assets are
fetched before rendering.
If we define an in the asset management system, later three.js doesn't have to internally create an. Creating the `` ourselves also gives
us more control and lets us reuse the texture across multiple entities. A-Frame
is also smart enough to set crossOrigin and other such attributes when
necessary.
To use the asset management system for an image texture:
- Add
<a-assets>to the scene. - Define the texture as an `` under
<a-assets>. - Give the `` an HTML ID (e.g.,
id="boxTexture"). - Reference the asset using the ID in DOM selector format (
src="#boxTexture").
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>Creating a Custom Environment (Optional)
Previously we had the environment component generate the environment. Though
it's good to know a bit on creating a basic environment for learning purposes.
Adding a Background to the Scene
We can add a background with <a-sky> that surrounds the scene.<a-sky>, which is a material applied to the inside of a large sphere, can be
a flat color, a 360° image, or a 360° video. For example, a dark gray
background would be:
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>Or we can use an image texture to get a 360° background image by usingsrc instead of color:
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
<img id="skyTexture"
src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky src="#skyTexture"></a-sky>
</a-scene>Adding a Ground
To add a ground, we can use <a-plane>. By default, planes are
oriented parallel to the XY axis. To make it parallel to the ground, we need to
orient it along the XZ axis. We can do so by rotating the plane negative
90° on the X-axis.
<a-plane rotation="-90 0 0"></a-plane>We'll want the ground to be large, so we can increase the width and height. Let's
make it 30-meters in each direction:
<a-plane rotation="-90 0 0" width="30" height="30"></a-plane>Then we can apply an image texture to our ground:
<a-assets>
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
</a-assets>
<a-plane src="#groundTexture" rotation="-90 0 0" width="30" height="30"></a-plane>To tile our texture, we can use the repeat attribute. repeat takes two
numbers, how many times to repeat in the X direction and how many times to
repeat in the Y direction (commonly referred to as U and V for textures).
<a-plane src="#groundTexture" rotation="-90 0 0" width="30" height="30"
repeat="10 10"></a-plane>Tweaking Lighting
We can change how the scene is lit by using <a-light>s. By default
if we don't specify any lights, A-Frame adds an ambient light and a directional
light. If A-Frame didn't add lights for us, the scene would be black. Once we
add lights of our own, however, the default lighting setup is removed and
replaced with our setup.
We'll add an ambient light that has a slight blue-green hue that matches the
sky. Ambient lights are applied to all entities in the scene (given they have
the default material applied at least).
We'll also add a point light. Point lights are like light bulbs; we can
position them around the scene, and the effect of the point light on an entity
depends on its distance to the entity:
<a-scene>
...
<a-light type="ambient" color="#445451"></a-light>
<a-light type="point" intensity="2" position="2 4 4"></a-light>
...
</a-scene>Adding Animation
We can add animations to the box using A-Frame's built-in animation
system. Animations interpolate or tween a value over time. We can
set the animation component on the entity. Let's have the box hover up and down
to add some motion to the scene.
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"
animation="property: object3D.position.y; to: 2.2; dir: alternate; dur: 2000; loop: true"></a-box>
</a-scene>We tell the animation component to:
- Animate the entity's object3D's position's Y axis.
- Animate to
2.2which is 20 centimeters higher. - Alternate the dir (direction) of the animation on each repeated cycle of the animation so it goes back and forth.
- Last for 2000 millisecond dur (duration) on each cycle.
- Loop or repeat the animation forever.

Adding Interaction
Let's add interaction with the box: when we look at the box, we'll increase the
size of the box, and when we "click" on the box, we'll make it spin.
Given that many developers currently do not have proper VR hardware with
controllers, we'll focus this section on using basic mobile and desktop inputs
with the built-in cursor component. The cursor component by default provides
the ability to "click" on entities by staring or gazing at them on mobile, or
on desktop, looking at an entity and click the mouse. But know that the cursor
component is just one way to add interactions, things open up if we have access
to actual controllers.
To have a visible cursor fixed to the camera, we place the cursor as a child
of the camera as explained above in Parent and Child Transforms.
Since we didn't specifically define a camera, A-Frame included a default camera
for us. But since we need to add a cursor as a child of the camera, we will
need to now define <a-camera> containing <a-cursor>:
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"
animation="property: object3D.position.y; to: 2.2; dir: alternate; dur: 2000; loop: true"></a-box>
<a-camera>
<a-cursor color="#FAFAFA"></a-cursor>
</a-camera>
</a-scene>If we check the documentation of the cursor component that<a-cursor> wraps, we see that it emits hover events such as mouseenter,mouseleave as well as click.
Event Listener Component (Intermediate)
One way to manually handle the cursor events is to add an event listener with
JavaScript just like we would with a normal DOM element. If
you aren't comfortable with JavaScript, you may skip to Animating on
Events below.
In JavaScript, we grab the box with querySelector, useaddEventListener, and then setAttribute
to make the box grow its scale when its hovered over. Note that A-Frame adds
features to setAttribute to work with multi-property components. We can pass
a full {x, y, z} object as the second argument.
<script>
var boxEl = document.querySelector('a-box');
boxEl.addEventListener('mouseenter', function () {
boxEl.setAttribute('scale', {x: 2, y: 2, z: 2});
});
</script>But a much more robust method would be to encapsulate this logic into an
A-Frame component. This way, we don't have to wait for the scene to load, we
don't have to run query selectors because components give us context, and
components can be reused and configured versus having an uncontrolled script
running on the page. And better yet would be to skip calling .setAttribute
and set the value on the this.el.object3D.scale directly for performance.
<script>
AFRAME.registerComponent('scale-on-mouseenter', {
schema: {
to: {default: '2.5 2.5 2.5', type: 'vec3'}
},
init: function () {
var data = this.data;
var el = this.el;
this.el.addEventListener('mouseenter', function () {
el.object3D.scale.copy(data.to);
});
}
});
</script>We can attach this component to our box straight from HTML:
<script>
AFRAME.registerComponent('scale-on-mouseenter', {
// ...
});
</script>
<a-scene>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"
animation="property: object3D.position.y; to: 2.2; dir: alternate; dur: 2000; loop: true"
scale-on-mouseenter></a-box>
</a-scene>Animating on Events
The animation component has a feature to start its animation when the entity
emits an event. This can be done through the startEvents attribute, which
takes a comma-separated list of event names.
We can add two animations for the cursor component's mouseenter andmouseleave events to change the box's scale, and one for rotating the box
around the Y-axis on click. Note that an entity can have multiple animations
by suffixing the attribute name with __:
<a-box
src="#boxTexture"
position="0 2 -5"
rotation="0 45 45"
scale="2 2 2"
animation__position="property: object3D.position.y; to: 2.2; dir: alternate; dur: 2000; loop: true"
animation__mouseenter="property: scale; to: 2.3 2.3 2.3; dur: 300; startEvents: mouseenter"
animation__mouseleave="property: scale; to: 2 2 2; dur: 300; startEvents: mouseleave"
animation__click="property: rotation; from: 0 45 45; to: 0 405 45; dur: 1000; startEvents: click"></a-box>Adding Audio
Audio is important for providing immersion and presence in VR. Even adding
simple white noise in the background goes a long way. We recommend having some
audio for every scene. One way would be to add an <audio> element to our
HTML (preferably under <a-assets>) to play an audio file:
<a-scene>
<a-assets>
<audio src="https://cdn.aframe.io/basic-guide/audio/backgroundnoise.wav" autoplay
preload></audio>
</a-assets>
</a-scene>Or we can add positional audio using <a-sound>. This makes the sound get
louder as we approach it and get softer as we distance from it. We could place
the sound in our scene using position.
<a-scene>
<a-sound src="https://cdn.aframe.io/basic-guide/audio/backgroundnoise.wav" autoplay="true"
position="-3 1 -4"></a-sound>
</a-scene>Adding Text
A-Frame comes with a text component. There are several ways to render
text, each with their own advantages and disadvantages. A-Frame comes with an SDF
text implementation using three-bmfont-text that is
relatively sharp and performant:
<a-entity text="value: Hello, A-Frame; color: #FAFAFA; width: 5; anchor: align"
position="-0.9 0.2 -3"
scale="1.5 1.5 1.5"></a-entity>Play With It!
And that's the basic example!
View the example (basic environment)
View the example (custom environment)
Guides Building A Minecraft Demo (`docs/guides/building-a-minecraft-demo.md`)
title: Building a Minecraft Demo
type: guides
layout: docs
parent_section: guides
order: 10
examples:
- title: Aincraft
src: https://github.com/aframevr/aframe/tree/master/examples/docs/aincraft/
View the source code, or try out the demo
Let's build a basic Minecraft (voxel builder) demo that targets room scale VR
with controllers (e.g., Quest, Vive, Rift). The example will be minimally usable on
mobile and desktop.
Example Skeleton
We'll start off with this skeleton HTML:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<body>
<a-scene>
</a-scene>
</body>Adding a Ground
<a-plane> and <a-circle> are basic primitives that are commonly used for
adding a ground. We'll be using <a-cylinder> to better work with the
raycasters our controllers will be using. The cylinder will have a radius of 30
meters to match the radius of the sky we'll add later. Note that A-Frame units
are in meters to match the real-world units returned from the WebXR API.
The texture of the ground we'll be using is hosted athttps://cdn.aframe.io/a-painter/images/floor.jpg". We'll add the texture to
our assets, and create a thin cylinder entity pointing to that texture:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<a-scene>
<a-cylinder id="ground" src="https://cdn.aframe.io/a-painter/images/floor.jpg" radius="32" height="0.1"></a-cylinder>
</a-scene>See a live version here
Preloading Assets
Specifying a URL via the src attribute will load the texture at runtime.
Since network requests can negatively impact render performance, we can
preload the texture such that the scene doesn't start rendering until its
assets have been fetched. We can do this using the asset management
system.
We place <a-assets> into our <a-scene>, place assets (e.g., images, videos,
models, sounds) into <a-assets>, and point to them from our entities via a
selector (e.g., #myTexture).
Let's move our ground texture to <a-assets> to be preloaded using an
`` element:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<a-scene>
<a-assets>
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
</a-assets>
<a-cylinder id="ground" src="#groundTexture" radius="32" height="0.1"></a-cylinder>
</a-scene>See a live version here
Adding a Background
Let's add a 360° background to our <a-scene> with the <a-sky>
element. <a-sky> is a large 3D sphere with a material mapped on the
inside. Just like a normal image, <a-sky> can take an image path with src.
This ultimately lets us do immersive 360° images with one line of HTML. As
an exercise later, try using some 360° images from Flickr's
equirectangular pool.
We could add a plain color background (e.g., <a-sky color="#333"></a-sky>) or
a gradient, but let's add a textured background with an image. The
image we're using is hosted at https://cdn.aframe.io/a-painter/images/sky.jpg.
The image texture we are using covers semi-sphere so we'll chop our sphere in
half with theta-length="90", and we'll give our sphere a radius of 30 meters
to match the ground:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<a-scene>
<a-assets>
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
<img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
</a-assets>
<a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>
<a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
</a-scene>See a live version here
Adding Voxels
Voxels in our VR application will be like <a-box> but attached with a few
custom A-Frame components. But first let's go over the entity-component
pattern. Let's see how the easy-to-use primitives such as <a-box> are
composed under the hood.
This section will later do a deeper dive into the implementation of a couple
A-Frame components. In practice though, we'd often get to use components via
HTML already written by A-Frame community developers rather than building them
from scratch.
Entity-Component Pattern
Every single object in an A-Frame scene is <a-entity>, which by itself
doesn't do anything, like an empty ``. We plug in components (not to be
confused with Web or React Components) to that entity to provide with
appearance, behavior , and logic.
For a box, we attach and configure A-Frame's basic geometry and material
components. Components are represented as HTML attributes, and component
properties are defined like CSS styles by default. Here's what <a-box> looks
like decomposed to its fundamental components. <a-box> wraps the components:
<a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width: 0.5"
material="color: red; shader: standard"></a-entity>The benefit of components is that they are composable. We can mix and match
from a bunch of existing components to construct different types of objects.
In 3D development, the possible types of objects we construct are infinite in
number and complexity, and we need an easy way of defining new types of objects
rather than through traditional inheritance. Contrast this to the 2D web where
we develop with a small pool of fixed HTML elements and plop them into a
hierarchy.
Random Color Component
Components in A-Frame are defined in JavaScript, and they have full access to
three.js and DOM APIs; they can do anything. We define all of our objects as a
bundle of components.
We'll put the pattern to action by writing an A-Frame component to set a random
color on our box. Components are registered with AFRAME.registerComponent. We
can define a schema, (the component's data) and lifecycle handler methods (the
component's logic). For the random color component, we won't be setting a schema
since it won't be configurable. But we will define the init handler, which is
called exactly once when the component is attached:
AFRAME.registerComponent('random-color', {
init: function () {
// ...
}
});For the random color component, we want to set a random color on the entity
that this component is attached to. Components have a reference to the entity
with this.el from the handler methods. And to change the color with
JavaScript, we change the material component's color property using.setAttribute(). A-Frame enhances the behavior of several DOM APIs a bit, but
the APIs mostly mirror vanilla web development. Read more about using JavaScript
and DOM APIs with A-Frame.
We'll also add the material component to the list of components that should
initialize before this one, just so our material isn't overwritten.
AFRAME.registerComponent('random-color', {
dependencies: ['material'],
init: function () {
// Set material component's color property to a random color.
this.el.setAttribute('material', 'color', getRandomColor());
}
});
function getRandomColor() {
const letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}After the component is registered, we can attach this component straight from
HTML. All code written within A-Frame's framework is extending HTML, and
those extensions can be used on other objects and in other scenes. The
beautiful thing is that a developer could write a component that adds physics
to an object, and then someone that doesn't even know JavaScript could add
physics to their scene!
Take our box entity from earlier, we attach the random-color HTML attribute
to plug in the random-color component. We'll save the component as a JS file
and include it before the scene:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="components/random-color.js"></script>
<a-scene>
<a-assets>
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
<img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
</a-assets>
<a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width: 0.5"
material="shader: standard"
position="0 0.5 -2"
random-color></a-entity>
<a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>
<a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
</a-scene>See a live version here
Components can be plugged into any entity without having to create or extend a
class like we'd have to in traditional inheritance. If we wanted to attach it
to say, <a-sphere> or <a-obj-model>, we could!
<a-sphere random-color></a-sphere>
<a-obj-model src="model.obj" random-color></a-obj-model>if we wanted to share this component for other people to use, we could too.
There is a community-maintained Component Directory that lists
many handy components from the ecosystem, similar to the Unity Asset Store.
If we developed our application using components, all our code is inherently
modular and reusable!
Snap Component
We'll have a snap component to snap our boxes to a grid so they aren't
overlapping. We won't get into the details of how this component is
implemented, but you can check out the snap component's source
code
(20 lines of JavaScript).
We attach the snap component to our box so that it snaps to every half meter,
also with an offset to center the box:
<a-entity
geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
material="shader: standard"
random-color
snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-entity>Now we have a box entity represented as a bundle of components that can be used
to describe all the voxels in our scene.
Mixins
We can create a mixin to define a reusable bundle of components.
Instead of <a-entity>, which adds an object to the scene, we'll describe it
using <a-mixin> which can be reused to create voxels like a prefab:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="components/random-color.js"></script>
<script src="components/snap.js"></script>
<a-scene>
<a-assets>
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
<img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
<a-mixin id="voxel"
geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
material="shader: standard"
random-color
snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-mixin>
</a-assets>
<a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>
<a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
<a-entity mixin="voxel" position="-1 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 1 -2"
animation="property: rotation; to: 0 360 0; loop: true"></a-entity>
<a-entity mixin="voxel" position="1 0 -2"></a-entity>
</a-scene>And we've added voxels using that mixin:
<a-entity mixin="voxel" position="-1 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 1 -2"
animation="property: rotation; to: 0 360 0; loop: true"></a-entity>
<a-entity mixin="voxel" position="1 0 -2"></a-entity>See a live version here
Next, we'll be creating voxels dynamically through interaction using tracked
controllers. Let's start adding our hands to the application.
Adding Hand Controllers
Adding HTC Vive or Oculus Touch tracked controllers is easy:
<a-entity vive-controls="hand: left"></a-entity>
<a-entity vive-controls="hand: right"></a-entity>
<a-entity meta-touch-controls="hand: left"></a-entity>
<a-entity meta-touch-controls="hand: right"></a-entity>We'll be using hand-controls which abstracts and works with both Vive and
Rift controls, providing models of basic hand. We'll make the left hand
responsible for teleporting around and the right hand responsible for spawning
and placing blocks.
<a-entity id="teleHand" hand-controls="hand: left"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right"></a-entity>Adding Teleportation to the Left Hand
We'll plug in teleportation capabilities to the left hand such that we push a
thumbstick to show an arc coming out of the controller, and let go of the
thumbstick to teleport to the end of the arc. Before, we wrote our own A-Frame
components. But we can also use open source components already made from the
community and just use them straight from HTML!
To enable this, let's first define a player entity that wraps the controllers
and the camera:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<a-entity id="player">
<a-entity id="teleHand" hand-controls="hand: left"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right"></a-entity>
<a-camera></a-camera>
</a-entity>For teleportation, there's a component called blink-controls.
Following the README, we add the component via a <script> tag and just set
the blink-controls component on the controller on the entity:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
<a-entity id="player">
<a-entity id="teleHand" hand-controls="hand: left"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right"></a-entity>
<a-camera></a-camera>
</a-entity>By default, blink-controls will only teleport on the ground, but we can
specify with collisionEntities to teleport on the blocks and the ground
using selectors. This property is part of the API that theblink-controls
component was created with:
<a-entity id="teleHand" hand-controls="hand: left" blink-controls="collisionEntities: [mixin='voxel'], #ground"></a-entity>That's it! One script tag and one HTML attribute and we can teleport. For
more cool components, check out the A-Frame Component Directory.
Adding Voxel Spawner to the Right Hand
In WebXR, the ability to click on objects isn't built-in as it is in 2D
applications. We have to provide that ourselves. Fortunately, A-Frame has many
components to handle interaction. A common method for cursor-like clicking in
VR is to use a raycaster, a laser that shoots out and returns objects that it
intersects with. Then we implement the cursor states by listening to
interaction events and checking the raycaster for intersections.
A-Frame provides laser-controls component for controller laser interaction
that attaches the clicking laser to VR tracked controllers. Like theblink-controls component, we include the script tag and attach thelaser-controls component. This time to the right hand:
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
<a-entity id="teleHand" hand-controls="hand: left" blink-controls="collisionEntities: [mixin='voxel'], #ground"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right" laser-controls></a-entity>Now when we pull the trigger button on the tracked controllers,laser-controls will emit a click event on both the controller and the
entity it is intersecting at the time. Events such as mouseenter,mouseleave are also provided. The event contains details about the
intersection.
That provides us with the ability to click, but we'll have to wire up some code
to handle those clicks to spawn blocks. We can use an event listener anddocument.createElement:
document.querySelector('#blockHand').addEventListener(`click`, function (evt) {
// Create a blank entity.
var newVoxelEl = document.createElement('a-entity');
// Use the mixin to make it a voxel.
newVoxelEl.setAttribute('mixin', 'voxel');
// Get normal of the face of intersection and scale it down a bit
var normal = evt.detail.intersection.face.normal;
normal.multiplyScalar(0.25);
// Get the position of the intersection and add our scaled normal
var position = evt.detail.intersection.point;
position.add(normal);
// Set the position using intersection point. The `snap` component above which
// is part of the mixin will snap it to the closest half meter.
newVoxelEl.setAttribute('position', position);
// Add to the scene with `appendChild`.
this.appendChild(newVoxelEl);
});To generalize creating entities from an intersection event, we've created anintersection-spawn component that can be configured with any event and list
of properties. We won't go into the detail of the implementation, but you can
check out the simple intersection-spawn component source code on
GitHub. We attach intersection-spawn capabilities to the
right hand, and it's also a good idea to give the raycaster a half-meter buffer
to prevent voxels from spawning right at the controller:
<a-entity id="blockHand" hand-controls="hand: right" laser-controls raycaster="near: 0.5" intersection-spawn="event: click; mixin: voxel"></a-entity>Now when we click, we spawn voxels!
Adding Support for Mobile and Desktop
We see how we've built a custom type of object (i.e., a tracked hand controller
with a hand model that has click capabilities and spawns blocks on click) by
mixing together components. The wonderful thing with components is that they
are reusable in other contexts. We could even attach the intersection-spawn
component with the gaze-based cursor component so that we can also spawn
blocks on mobile and desktop, without changing a thing about the component!
<a-entity id="blockHand" hand-controls="hand: right" laser-controls raycaster="near: 0.5" intersection-spawn="event: click; mixin: voxel"></a-entity>
<a-camera>
<a-cursor intersection-spawn="event: click; mixin: voxel"></a-cursor>
</a-camera>Try It Out!
On desktop, we can drag to move the view and click to spawn blocks. We can
also move using WASD.
On mobile, we can pan the device around and tap to spawn blocks. If we have
a VR headset with a WebXR-capable browser (e.g., Meta Quest, HTC Vive, Oculus
Rift), we can view the demo in VR within the browser.
Introduction Best Practices (`docs/introduction/best-practices.md`)
title: Best Practices type: introduction layout: docs parent_section: introduction order: 13
A-Frame
The core structure of A-Frame is entity-component (ECS). Place and
structure application code within purely A-Frame components for reusability,
modularity, composability, decoupling, encapsulation, declarativeness, and
testability.
Do not do this:
<a-scene>
<a-box></a-box>
</a-scene>
<script>
// My JavaScript code here!
// ... NO!
</script>Place your code within A-Frame components so they are executed at the correct
time, to encapsulate reusable code, and to make use of the framework which is the
purpose of using A-Frame.
Do this:
<script>
AFRAME.registerComponent('code-that-does-this', {
init: function () {
// Code here.
console.log(this.el);
}
});
AFRAME.registerComponent('code-to-attach-to-box', {
init: function () {
// Code here.
console.log(this.el);
}
});
</script>
<a-scene code-that-does-this>
<a-box code-to-attach-to-box></a-box>
</a-scene>Performance
Performance is critical in VR. A high framerate must be maintained in order for
people to feel comfortable. Here are some ways to help improve performance of
an A-Frame scene:
- Use recommended hardware specifications.
- Use the stats component to keep an eye on various metrics (FPS,
vertex and face count, geometry and material count, draw calls, number of
entities). We want to maximize FPS and minimize everything else. - Limit draw calls as much as possible. Each geometry, object, model without
optimization is generally a draw call. Rule of thumb, try to keep under 300
maximum. Merge together all static meshes if possible. You
can use geometry-merger and then make use a three.js
material with vertex colors enabled. three.js geometries keep data such as
color, uvs per vertex that can be used to maintain and manipulate geometries
post-merge. - Make use of the asset management system to browser cache and
preload assets. - If using models, look to bake your lights into textures rather than relying
on real-time lighting and shadows. - Generally, the fewer number of entities and lights in the scene, the better.
- Make sure your textures' resolutions are sized to powers of two (e.g.,
256x256, 512x1024) in order to avoid the renderer having to resize the
texture during runtime. - Limit the number of faces and vertices on models.
- Some further techniques include geometry instancing, geometry
merging, level of detail (LOD). - When using raycasters or colliders, select which entities are to be raycasted
against rather than raycasting against every object in the scene. - When adding continuously running behaviors, use A-Frame component
tick
handlers to hook into the global render loop. Use utilities such asAFRAME.utils.throttleTickto limit the number of times thetickhandler
is run if appropriate. - Use the background component instead of
a-skyto define a
solid color as the scene background. This prevents the creation of
unnecessary geometry. - Update
position,rotation,scale, andvisibleat the three.js
level (el.object3D.position,el.object3D.rotation,el.object3D.scale,el.object3D.visible) to avoid overhead on.setAttribute. - If you need to create, remove and re-create many entities of the same type,
use the pool component to pre-generate and reuse entities. This
avoids the cost of creating entities on the fly and reduces garbage collection. - When using the animation component, animate values directly
which will skip.setAttributeand animate JS values directly.
For example, instead ofmaterial.opacity,
animatecomponents.material.material.opacity.
GPU Texture Preloading
Until non-blocking texture uploads to the GPU are available, try to draw all
materials and textures up front. When materials and textures are drawn for the
first time, the browser will hang and block while uploading to the GPU. We can
manually preload textures by calling:
document.querySelector('a-scene').renderer.setTexture2D(ourTexture, 0);We will try to come with a convenient API in A-Frame to do this automatically.
For example, this is apparent in the 360° image gallery. If we look at
the browser performance tools, there will be frame drops when switching to a
new image for the first time, but smooth transitions when switching back to
images for the second time.
Reuse materials and textures as much as possible, aiming for a small number
of unique materials. Texture atlases provide one efficient way to reuse
materials while giving the impression of more variety. Simpler three.js
materials such as MeshLambertMaterial or MeshBasicMaterial perform better
and are often sufficient for low-poly scenes.
In particular, pre-baked lighting on an unlit (Basic) material can
significantly improve performance. A-Frame's default PBR-based (Standard)
material is more physically realistic, but also more expensive and often
unnecessary in simple scenes.
Minimizing Garbage Collection in JavaScript
Avoid creating garbage and instantiating new JavaScript objects, arrays,
strings, and functions as much as possible. In the 2D web, it is not as big of
a deal to create a lot of JavaScript objects since there is a lot of idle time
for the garbage collector to run. For VR, garbage collection may cause dropped
frames as it pauses to clean up memory. To avoid this, we try to minimize
allocation of memory and hold onto objects to prevent them from getting garbage
collected.
Learn more about detecting allocations and garbage collection in
Firefox and Chrome performance tools.
Try to avoid patterns such as Object.keys(obj).forEach(function () { });,
which create new arrays, functions, and callbacks versus using for (key in obj). Or for array iteration, avoid .forEach and use a simple for loop
instead. Avoid unnecessary copying of objects, using methods likeutils.extend(target, source) instead of utils.clone or .slice.
If emitting an event, try to reuse the same object for event details:
AFRAME.registerComponent('foo', {
init: function () {
this.someData = [];
this.evtDetail = {someData: this.someData};
},
tick: function (time) {
this.someData.push(time);
this.el.emit('bar', this.evtDetail);
}
});All of the suggestions above are especially important in tick() handlers,
where they will be running on every frame.
More articles on reducing garbage collector activity:
- Best Practices for Reducing Garbage Collector Activity in JS
- How to Write Low Garbage Real-Time JavaScript
`tick` Handlers
In component tick handlers, be frugal on creating new objects. Try to reuse
objects. A pattern to create private reusable auxiliary variables is with a
closure. Below we create a helper vector and quaternion and reuse them between
frames, rather than creating new ones on each frame. Be careful that these
variables do not hold state because they will be shared between all instances
of the component. Doing this will reduce memory usage and garbage collection:
AFRAME.registerComponent('foo', {
tick: function () {
this.doSomething();
},
doSomething: (function () {
var helperVector = new THREE.Vector3();
var helperQuaternion = new THREE.Quaternion();
return function () {
helperVector.copy(this.el.object3D.position);
helperQuaternion.copy(this.el.object3D.quaternion);
};
})()
});Also if we continuously modify a component in the tick, make sure we pass the
same object for updating properties. A-Frame will keep track of the latest
passed object and disable type checking on subsequent calls for an extra speed
boost. Here is an example of a recommended tick function that modifies the
rotation on every tick:
AFRAME.registerComponent('foo', {
tick: function () {
var el = this.el;
var rotationTmp = this.rotationTmp = this.rotationTmp || {x: 0, y: 0, z: 0};
var rotation = el.getAttribute('rotation');
rotationTmp.x = rotation.x + 0.1;
rotationTmp.y = rotation.y + 0.1;
rotationTmp.z = rotation.z + 0.1;
el.setAttribute('rotation', rotationTmp);
}
});Again be careful what you do in tick functions, treat them as critical
performance code because they will be run 90 times per second. Consider
using utils.throttleTick to run your code at less frequent intervals.
VR Design
Designing for VR is different than designing for flat experiences. As a new
medium, there are new sets of best practices to follow, especially to maintain
user comfort and presence. This has been thoroughly written about so we defer
to these guides. Note that VR interaction design is fairly new, and nothing is
absolute:
Some things to note:
- The common golden rule is to never unexpectedly take control of the camera
away from users. - Units (such as for position) should be considered meters. This is because the
WebVR API returns pose in meters which is fed into most camera controls. By
considering units as meters, we achieve expected scale.
Make use of hands and controllers. For best experience, target your application
to a specific form factor versus watering it down for all platforms at once.
Introduction Developing With Threejs (`docs/introduction/developing-with-threejs.md`)
title: Developing with three.js type: introduction layout: docs parent_section: introduction order: 7 examples: []
Being a framework based on three.js, A-Frame provides full access to the
three.js API. We'll go over how to access the underlying three.js scene,
objects, and API that lay underneath A-Frame.
Relationship Between A-Frame and three.js Scene Graphs
- A-Frame's
<a-scene>maps one-to-one with a three.js scene. - A-Frame's
<a-entity>maps to one or more three.js objects. - three.js's objects have a reference to their A-Frame entity via
.el, which is
set by A-Frame.
Parent-Child Relationships
When A-Frame entities are nested in parent-child relationships, so are their
three.js objects. For example, take this A-Frame scene:
<a-scene>
<a-box>
<a-sphere></a-sphere>
<a-light></a-light>
</a-box>
</a-scene>The three.js scene graph will correspond and look like:
THREE.Scene
THREE.Mesh
THREE.Mesh
THREE.LightAccessing the three.js API
three.js is available as a global object on the window:
console.log(THREE);Working With three.js Objects
A-Frame is an abstraction on top of three.js, but we still operate with
three.js underneath. A-Frame's elements have doors that lead to three.js's
scene graph.
Accessing the three.js Scene
The three.js scene is accessible from the <a-scene> element as .object3D:
document.querySelector('a-scene').object3D; // THREE.SceneAnd every A-Frame entity also has a reference to <a-scene> via .sceneEl:
document.querySelector('a-entity').sceneEl.object3D; // THREE.SceneFrom a component, we access the scene through its entity
(i.e., this.el):
AFRAME.registerComponent('foo', {
init: function () {
var scene = this.el.sceneEl.object3D; // THREE.Scene
}
});Accessing an Entity's three.js Objects
Every A-Frame entity (e.g., <a-entity>) has its ownTHREE.Object3D, more specifically a THREE.Group that
contains different types of Object3Ds. The root THREE.Group of an entity is
accessed via .object3D:
document.querySelector('a-entity').object3D; // THREE.GroupEntities can be composed of multiple types of Object3Ds. For example,
an entity can be both a THREE.Mesh and a THREE.Light by having both
a geometry component and light component:
<a-entity geometry light></a-entity>Components add the mesh and light under the entity's root THREE.Group.
References to the mesh and light are stored as different types of three.js
objects in the entity's .object3DMap.
console.log(entityEl.object3DMap);
// {mesh: THREE.Mesh, light: THREE.Light}But we can access them through the entity's .getObject3D(name) method:
entityEl.getObject3D('mesh'); // THREE.Mesh
entityEl.getObject3D('light'); // THREE.LightNow let's see how these three.js objects were set in the first place.
Setting an `Object3D` on an Entity
Setting an Object3D on an entity adds the Object3D to the entity's Group,
which makes the newly set Object3D part of the three.js scene. We set theObject3D with the entity's .setObject3D(name) method where the name
denotes the Object3Ds purpose.
For example, to set a point light from within a component:
AFRAME.registerComponent('pointlight', {
init: function () {
this.el.setObject3D('light', new THREE.PointLight());
}
});
// <a-entity light></a-entity>We set the light with the name light. To later access it, we can use the
entity's .getObject3D(name) method as described before:
entityEl.getObject3D('light');And when we set a three.js object on an A-Frame entity, A-Frame will set a
reference to the A-Frame entity from the three.js object via .el:
entityEl.getObject3D('light').el; // entityElRemoving an `Object3D` From an Entity
To remove an Object3D from an entity, and consequently the three.js scene, we
can use the entity's .removeObject3D(name) method. Going back to our example
with the point light, we remove the light when the component is detached:
AFRAME.registerComponent('pointlight', {
init: function () {
this.el.setObject3D('light', new THREE.PointLight());
},
remove: function () {
// Remove Object3D.
this.el.removeObject3D('light');
}
});Transforming Between Coordinate Spaces
Every object and the scene (world) in general has their own coordinate space. A
parent object's position, rotation, and scale transformations are applied to
its children's position, rotation, and scale transformations. Consider this scene:
<a-entity id="foo" position="1 2 3">
<a-entity id="bar" position="2 3 4"></a-entity>
</a-entity>From the world's reference point, foo has position (1,2,3) and bar has position
(3, 5, 7) since foo's transformations apply onto bar's. From foo's reference
point, foo has position (0, 0, 0) and bar has position (2, 3, 4).
Often we will want to transform between these reference points and coordinate
spaces. Above was a simple example, but we might want to do operations such as
finding the world-space coordinate of bar's position, or translate an arbitrary
coordinate into foo's coordinate space. In 3D programming, these operations are
accomplished with matrices, but three.js provides helpers to make them easier.
Local to World Transforms
Normally, we'd need to call .updateMatrixWorld () on parent Object3Ds, but
three.js defaults Object3D.matrixAutoUpdate to true. We can use three.js's.getWorldPosition (vector) and .getWorldQuaternion (quaternion).
To get the world position of an Object3D:
var worldPosition = new THREE.Vector3();
entityEl.object3D.getWorldPosition(worldPosition);To get the world rotation of an Object3D:
var worldQuaternion = new THREE.Quaternion();
entityEl.object3D.getWorldQuaternion(worldQuaternion);three.js Object3D has more functions available for local-to-world transforms:
.localToWorld (vector).getWorldDirection (vector).getWorldQuaternion (quaternion).getWorldScale (vector)
World to Local Transforms
To obtain a matrix that transforms from world to an object's local space, get
the inverse of the object's world matrix.
var worldToLocal = new THREE.Matrix4().getInverse(object3D.matrixWorld)Then we can apply that worldToLocal matrix to anything we want to transform:
anotherObject3D.applyMatrix(worldToLocal);Introduction Entity Component System (`docs/introduction/entity-component-system.md`)
title: Entity-Component-System
type: introduction
layout: docs
order: 5
examples:
- title: Community Components in Action
src: https://aframe.io/aframe/examples/docs/community-components/ - title: Animated Lights
src: https://aframe.io/aframe/examples/showcase/dynamic-lights/
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:
- Greater flexibility when defining objects by mixing and matching reusable parts.
- Eliminates the problems of long inheritance chains with complex interwoven functionality.
- Promotes clean design via decoupling, encapsulation, modularization, reusability.
- Most scalable way to build a VR application in terms of complexity.
- Proven architecture for 3D and VR development.
- Allows for extending new features (possibly sharing them as community components).
On the 2D Web, we lay out elements that have fixed behavior in a hierarchy. 3D
and VR is different; there are infinite types of possible objects that have
unbounded behavior. ECS provides a manageable pattern to construct types of
objects.
Below are great introductory materials to ECS architecture. We recommend
skimming through them to get a better grasp of the benefits. ECS is well-suited
for VR development, and A-Frame is based entirely around this paradigm:
- Entity-component-system on Wikipedia
- What is an Entity System? by Adam Martin
- Decoupling Patterns — Component on Game Programming Patterns
- Evolve Your Hierarchy by Mick West
A well-known game engine implementing ECS is Unity. Although there are pain
points in cross-entity communication, we'll see how A-Frame, the DOM, and
declarative HTML really make ECS shine.
Concept
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 ``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.
Examples
Some abstract examples of different types of entities built from composing
together different components:
Box = Position + Geometry + MaterialLight Bulb = Position + Light + Geometry + Material + ShadowSign = Position + Geometry + Material + TextVR Controller = Position + Rotation + Input + Model + Grab + GesturesBall = Position + Velocity + Physics + Geometry + MaterialPlayer = Position + Camera + Input + Avatar + Identity
As another abstract example, imagine we want to build a car entity by
assembling components:
- We can attach a
materialcomponent that has properties such as "color" or
"shininess" that affects the appearance of the car. - We can attach an
enginecomponent that has properties such as "horsepower" or
"weight" that affects the functionality of the car. - We can attach a
tirecomponent that has properties such as "number of
tires" or "steering angle" that affects the behavior of the car.
So we can create different types of cars by varying the properties of thematerial, engine, and tire component. The material, engine, andtire components don't have to know about each other and can even be used in
isolation for other cases. We could mix and match them to create even
different types of vehicles:
- To create a boat entity: remove the
tirecomponent. - To create a motorcycle entity: change
tirecomponent's number of tires to 2,
configure theenginecomponent to be smaller. - To create an airplane entity: attach
wingandjetcomponents.
Contrast this to traditional inheritance where if we want to extend an object,
we would have to create a large class that tries to handle everything or an
inheritance chain.
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 theAFRAME.registerComponent (name, definition)
API. - Systems are represented by
<a-scene>'s HTML attributes. System are
similar to components in definition. Systems are registered via theAFRAME.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 toHTMLElement.style 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).

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:
<a-entity
tracked-controls
vive-controls
meta-touch-controls
hand-controls
laser-controls
sphere-collider
grab
throw
event-set="_event: grabstart; visible: false"
event-set="_event: grabend; visible: true"
>Declarative DOM-Based 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:
- 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'). - 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'). - APIs for Lifecycle Management with DOM APIs: 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. - 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])'). - 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:
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 when 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:
<a-entity foo="bar: 5; baz: bazValue"></a-entity>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:
index.html
components/
ball.js
collidable.js
grabbable.js
enemy.js
scoreboard.js
throwable.jsHigher-Order Components
Components can set other components on the entity, making them a higher-order
or higher-level component in abstraction.
For example, the cursor component sets and builds on top of the
raycaster component. Or the hand-controls
component sets and builds on top of the vive-controls
component and meta-touch-controls
component which in turn build on top of the
tracked-controls component.
Community Component Ecosystem
Components can be shared into the A-Frame ecosystem for the community to use.
The wonderful thing about A-Frame's ECS is extensibility. An experienced
developer can develop a physics system or graphics shader components, and an
novice developer can take those components and use them in their scene from
HTML just by dropping in a <script> tag. We can use powerful published
components without having to touch JavaScript.
Where to Find Components
There are hundreds of components out in the wild. We try our best to make them
discoverable. If you develop a component, please submit it through these
channels to share!
npm
Most A-Frame components are published on npm as well as GitHub. We can use
npm's search to search for packages tagged with aframe-component.
This is a great place to look for a more complete list of components.
For a list of components with the A-Frame version that the component
was last tested with, check out the community-maintained Component Directory
on the A-Frame Wiki.
GitHub Projects
Many A-Frame applications are developed purely from components, and many of
those A-Frame applications are open source on GitHub. Their codebases will
contain components that we can use directly, refer to, or copy from. Projects
to look at include:
A-Frame Blog
The A-Frame Blog archives include details of components as they were released or updated, and can be a good place to find links to components.
A-Frame Wiki
The A-Frame Wiki is a useful community-driven initiative that collects information and tips about available A-Frame components. Everyone is encouraged to participate. It's very easy to add and edit information.
Using a Community Component
Once we find a component that we want to use, we can include the component as a<script> tag and use it from HTML.
For example, let's use the particle system component:
Using unpkg
First, we have to grab a CDN link to the component JS file. The documentation
of the component will usually have a CDN link or usage information. But a way
to get the most up-to-date CDN link is to use unpkg.com.
unpkg is a CDN that automatically hosts everything that is published to npm.
unpkg can resolve semantic versioning and provide us the version of the
component we want. A URL takes the form of:
https://unpkg.com/<npm package name>@<version>/<path to file>If we want the latest version, we can exclude the version:
https://unpkg.com/<npm package name>/<path to file>Rather than typing in the path to the built component JS file, we can excludepath to file to be able to browse the directories of the component package.
The JS file will usually be in a folder called dist/ or build/ and end with.min.js.
For the particle system component, we head to:
https://unpkg.com/aframe-particle-system-component/Note the ending slash (/). Find the file we need, right click, and hit Copy
Link to Address to copy the CDN link into our clipboard.
Including the Component JS File
Then head to our HTML. Under the <head>, after the A-Frame JS <script>
tag, and before <a-scene>, we will include our JS file with a <script>
tag.
For the particle system component, the CDN link we found earlier (at time of
writing) was:
https://unpkg.com/@c-frame/[email protected]/dist/aframe-particle-system-component.min.jsNow we can include it into our HTML:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/@c-frame/[email protected]/dist/aframe-particle-system-component.min.js"></script>
</head>
<body>
<a-scene>
</a-scene>
</body>
</html>Using the Component
Follow the documentation of the component on how to use it in implementation.
But generally, the usage involves attaching the component to an entity and
configuring it. For the particle system component:
Now we can include it into our HTML:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/@c-frame/[email protected]/dist/aframe-particle-system-component.min.js"></script>
</head>
<body>
<a-scene>
<a-entity particle-system="preset: snow" position="0 0 -10"></a-entity>
</a-scene>
</body>
</html>Using jsdelivr for CDN
JSDELIVR is an alternative CDN to unpkg. One benefit of JSDELIVR is that it can download files from GitHub as well as NPM.
You can convert unpkg URLs to JSDELIVR URLs using this link: https://www.jsdelivr.com/unpkg
You can convert GitHub URLs to JSDELIVR URLs using this link: https://www.jsdelivr.com/github
Example
Below is a complete example of using various community components from the
Registry and using the JSDELIVR CDN. This example can also be viewed in the A-Frame Examples.
<html>
<head>
<title>Community Components Example</title>
<meta name="description" content="Community Components Example">
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/@c-frame/[email protected]/dist/aframe-particle-system-component.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-simple-sun-sky@^1.2.2/simple-sun-sky.js"></script>
<script src="https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene>
<a-entity id="rain" particle-system="preset: rain; color: #24CAFF; particleCount: 5000"></a-entity>
<a-entity id="sphere" geometry="primitive: sphere"
material="color: #EFEFEF; shader: flat"
position="0 0.15 -5"
light="type: point; intensity: 5"
animation="property: position; easing: easeInOutQuad; dir: alternate; dur: 1000; to: 0 -0.10 -5; loop: true"></a-entity>
<a-entity id="ocean" ocean="density: 20; width: 50; depth: 50; speed: 4"
material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
rotation="-90 0 0"></a-entity>
<a-simple-sun-sky sun-position="1 0.4 0"></a-simple-sun-sky>
<a-entity id="light" light="type: ambient; color: #888"></a-entity>
</a-scene>
</body>
</html>Introduction Faq (`docs/introduction/faq.md`)
title: FAQ type: introduction layout: docs parent_section: introduction order: 14
How is A-Frame's performance?
A-Frame can achieve native-like latency and framerate with proper browsers
(e.g., Firefox with WebVR). For example, A-Painter is Tilt
Brush in the browser that can smoothly run 90 frames per second
and can be indiscernible from native.
A-Frame being based on HTML is not an issue. While the browser's 2D layout has
been a primary performance concern for normal web applications, A-Frame uses
Custom Elements to act merely as data containers and does not trigger the
layout engine. 3D operations are done in memory with minimal overhead and
are rendered with WebGL, which binds to OpenGL or Direct3D.
Some measures that A-Frame takes to minimize overhead include:
- Making
setAttributesynchronous with a reduced code path. Modifying an
entity's position viasetAttribute('position', {x: 1, y: 2, z: 3})almost
directly touches the underlying three.js objects. The overhead involves
comparing new data to old data to determine whether a change needs to be triggered
and invoking lifecycle handlers. - Not serializing data back to the DOM. When changing an entity's properties, the actual
HTML as seen in the browser's DOM inspector is not updated to reduce stringification
operations. This makes most operations done in memory, a tiny bit similar to
Virtual DOM. - Keeping everything under a single
requestAnimationFrameand letting
components hook into a single global render loop via thetickhandler. - Caching HTML attributes values.
- Caching assets, materials, textures, geometries.
- Providing community components that implement performance techniques used in the
3D and game industry (e.g., geometry instancing, level-of-detail, object pooling).
A-Frame provides reasonable defaults that yield good performance for most
common use cases. However, performance is ultimately determined by the
complexity and characteristics of each individual application. To get the best
use of resources, we will need deeper understanding about 3D graphics. See
best performance practices and guidelines to get started.
Why is my experience not showing VR or AR buttons?
The WebXR standard requires a secure context.
You have to serve your content over HTTPS.
For the AR button, you need to explicitly enable it with<a-scene xr-mode-ui="XRMode: xr"> to have both the VR and AR buttons.
You can use XRMode: ar to show only the AR button.
Note that the vr-mode-ui component was renamed to xr-mode-ui since A-Frame 1.5.0.
Why does my asset (e.g., image, video, model) not load?
First, if you are doing local development, make sure you are using a local
server so that asset requests work properly.
If you are loading the asset from a different domain, make sure that the asset
is served with cross-origin resource sharing (CORS) headers. You could
either find a host to serve the asset with CORS headers, or place the asset on
the same domain (directory) as your application.
If you are trying to load a video, make sure the browser supports the video
(i.e., encoding, framerate, size).
Video autoplay policies are getting more and more strict and rules might vary across browsers. Mandatory user gesture is now commonly enforced. For maximum compatibility, you can offer a button that the user can click to start video playback. Simple sample code can be found in the docs. Pay particular attention to the play-on-click component
Read the Hosting and Publishing guide for more
information.
Why is the HTML not updating when I check the browser inspector?
If you open up your browser's developer tools, you'll see that the HTML
attribute values are empty.
To improve performance, A-Frame does not update the HTML to save on
stringification operations. This also means mutation observations will not
fire. Use the debug component or .flushToDOM methods
if you need to sync to the DOM.
Why does my video not play?
Mobile and now desktop browsers have limitations playing inline video.
Because of an iOS platform restriction in order to get inline video
(with or without autoplay), we must:
- Set the
<meta name="apple-mobile-web-app-capable" content="yes">meta tag (will be injected if missing). - Set the
playsinlineattribute to the video element (is automatically added to all videos). - Possibly pin the webpage to the homescreen for older iOS versions.
Mobile and desktop browsers have been tightening the video autoplay policies to preserve battery and avoid intrusive advertisements. Most browsers now require a user action (such as a click or tap event) to start video playback:
-Chrome for Android
-Chrome desktop
-Safari
-Firefox
There's an A-Frame example that includes the necessary logic to request the user clicking or tapping to start video playback. The source code is also available
We do not focus too much on video, but below are GitHub issues that may contain helpful information from community:
- Videos and videospheres don't work on mobile
- Document iOS video encoding restrictions
- Official videosphere demo does not work on mobile
How do I display ``s or render HTML in A-Frame?
There is no way for the browser to display s within WebGL. While it is possible to overlay an on top of the canvas, the `` will
not display in VR nor can it integrate with the scene.
Though, we can render basic HTML and CSS as a texture without interactivity.
We can paint to a <canvas> and use the canvas as source for a texture. There
are components in the ecosystem that enable this:
Which 3D model formats work?
The ideal format is the GL Transmission Format glTF (.gltf) since
glTF is feature-rich, compact, and efficient. glTF focuses on providing a
transmission format rather than an editor format and is more interoperable
with web technologies. Read more about glTF and A-Frame's glTF
component.
Wavefront (.obj) is also a well-known format but has some limitations
like the lack of animation and vertex color support.
Below are a couple basic examples for using models:
Where can I find assets?
In general, awesome-stock-resources is a great collection of
free assets.
For images, check out textures.com.
For 360° images, search for equirectangular images on Flickr.
For 3D models, check out:
For sounds, check out:
Can I render Vimeo videos as a texture?
Yes. Vimeo has an A-Frame plugin, but rendering is limited to just videos from your personal Vimeo account.
Can I render YouTube videos as a texture?
No. You could proxy YouTube videos as a texture or download them locally
to serve, but that is against their terms of service.
Can I add links to my scene?
Browsers provide the ability to go from WebVR page to WebVR page via thevrdisplayactivate event described in the WebVR specification. Currently, not
all browsers implement this. Firefox with WebVR implements this. A link
component for link traversal (example) was released with A-Frame 0.6.0:
<a-entity link="on: click; href: https://aframe.io/aframe/examples/docs/aincraft/"></a-entity>Can I prevent the camera from going through obstacles?
This depends on what devices you plan to support and how you allow users to
navigate your scene. For most VR experiences, follow best practices and only
move the camera proportionately to the user's motion.
Don't block the camera if the user steps forward in a room-scale VR space. For
most VR applications it's better to do locomotion with methods such as using
teleportation controls, designing your scene to keep obstacles out
of the way or not require much movement, or explore more creative ways of
moving users through the world.
For non-VR desktop experiences with a gamepad or keyboard controls or for VR
scenes where the camera is inside a vehicle, you can add a physics
engine to prevent movement through obstacles.
What type of units does A-Frame use?
A-Frame uses meters with a 1:1 ratio since the WebVR API also uses meters. 5
units in A-Frame is equal to 5 meters in real life. Furthermore, when using
programs like Blender, configured in imperial or metric mode, measurements will
also translate 1:1. 10 feet in Blender will equal 10 feet in real life.
How is A-Frame different from VRML?
A-Frame is a JavaScript framework. Unlike VRML, A-Frame is not a 3D file
format, markup language, nor a standard. A-Frame embraces the Extensible Web
Manifesto. Only look at standardization as winning ideas emerge.
Technically, A-Frame is an entity-component-system game engine on top of
three.js. As it is a JavaScript framework, coding is to be expected for more
complex applications. Unlike 3D file formats, A-Frame provides power and
interactivity via full access to JavaScript, three.js, and Web APIs.
Does A-Frame support `X` feature?
A-Frame ships with a number of components and primitives. Being based on top of
an entity-component-system architecture, if a feature doesn't exist, you
can write or find a component to enable it. Or if one of
the standard components doesn't fit your use cases, you can copy and modify
it.
Read Where to Find Components for more information.
Does A-Frame support `X` library or framework?
A-Frame is built on top of the DOM so most libraries and frameworks work
including jQuery, HTMX, React, SolidJS, Vue and Svelte.
Some meta frameworks like TanStack Start and SolidStart hide the index.html,
so you may encounter issues adding additional script tags and template tags (for
Networked-Aframe).
We advice you to use a template for your framework that still has an index.html
you can edit and render the UI to a specific div while keeping your <a-scene>
directly in index.html. That way you can easily follow A-Frame examples.
If you're creating a complex app with several routes and only specific routes
render an A-Frame scene, you'll want to add a router library and render<a-scene> via a component in your framework.
Reactive frameworks like SolidJS and latest versions of Vue and Svelte are
well suited for this because they doesn't do unnecessary computation to check
if something needs to be updated. You can do that also in React but be sure to wrap
your Scene and UI components with React.memo and proper usage of useMemo/useCallback
or better enable the React Compiler that does this for you to avoid any performance
issues.
The Vite build tool has templates for most of the popular frameworks.
You can follow the Vite Guide to create a new project
and then add the aframe <script> tag in the head and the <a-scene> tag in
the body.
For SolidJS and Networked-Aframe, you have a
dedicated tutorial explaining how
to set up a project, and using a router to render the scene for a specific
route. For React, it should be very similar.
Which headsets, browsers, devices, and platforms does A-Frame support?
Most of them. Read VR Headsets and WebVR Browsers for more
information.
How can I improve performance?
Read Best Practices — Performance for more information.
How can I get in touch with the A-Frame team?
We try to be responsive and helpful! We love questions, feedback, bug reports,
and pull requests:
- Got a question? Ask us using the A-Frame StackOverflow tag.
- Want to chat? Hang out with us on the community A-Frame Slack channel.
- Want to share? Tweet at us at @aframevr.
- Find an issue? File issues on the A-Frame GitHub repo.
Where is the roadmap?
The roadmap is on GitHub!
Do I call it "A-Frame" or "aframe" or "aframevr" or "aFrame"?
A-Frame!
Why do my textures render black?
Phones with Adreno 300 series GPUs are notoriously problematic. Set renderer precision to medium as a workaround. Real fix has to happen at the driver / device level.
Why is the gyroscope / magic window mode not working?
New browser policies require sites to prompt the user for permission before getting access to DeviceMotionEvents. Starting with iOS 13 DeviceMotionEvents are only available for pages served over https. Other browsers will also apply same policies and restrictions. A-Frame now incorporates customizable UI to request the necessary permissions to the user. Make sure to update to A-Frame latest version
Can I use A-Frame offline or self hosted?
Using A-Frame online sometimes is not possible or inconvenient, for instance when traveling or during public events with poor Internet connectivity. A-Frame is mostly self-contained so including the build (aframe.min.js) in your project will be sufficient in many cases. Some specific parts are lazy loaded and only fetched when used. This is for example the case of the fonts for the text component and the 3D models for controllers. In order to make an A-Frame build work either offline or without relying on A-Frame hosting infrastructure (typically cdn.aframe.io), you can monitor network requests on your browser console. This will show precisely what assets are being loaded and thus as required for your specific experience. Fonts can be found via FONT_BASE_URL in the whereas controllers via MODEL_URLS. Both can be modified in the source and included in your own custom build.
Alternatively one can set window.AFRAME_CDN_ROOT='./assets/' before loading AFrame. That directory can contain files from the assets repository, in particular the ./fonts and ./controllers directories. Note that not all files are required, for example for controllers only the .glb files are required, others can thus be safely removed. Once again it is good to check with the browser network inspector that all needed files are loading properly locally before going truly offline.
Can I load A-Frame as an ES module?
You can load A-Frame as an ES module using a side effect import. A-Frame will then initialize any <a-scene> in the document. It's still important to register any components or systems you need before this happens:
<head>
<script type="importmap">
{
"imports": {
"aframe": "https://aframe.io/releases/1.8.0/aframe.min.js",
}
}
</script>
<script type="module">
import 'aframe';
AFRAME.registerComponent('my-component', {
...
});
</script>
</head>If it's not possible to register everything synchronously to importing A-Frame, you can set the window.AFRAME_ASYNC flag. This prevents A-Frame from initializing <a-scene> tags, until you give a ready signal by calling window.AFRAME.emitReady(). Note that this flag must be set before importing A-Frame, as shown in the following example:
window.AFRAME_ASYNC = true;
await import('aframe');
// Asynchronously register components/systems
window.AFRAME.emitReady();Since version 1.7.0, A-Frame ships an ES module bundle without the three dependency.
Developers can import from three and three/addons and avoid the
"Multiple instances of Three.js being imported." warning. Add the three dependency in the importmap like the example below.
Make sure the three and A-Frame versions are compatible. See browser console (or package.json) to see what THREE version A-Frame ships with by default.
<head>
<script type="importmap">
{
"imports": {
"aframe": "https://aframe.io/releases/1.8.0/aframe.module.min.js",
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.min.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"aframe-extras/controls": "https://cdn.jsdelivr.net/gh/c-frame/[email protected]/dist/aframe-extras.controls.min.js"
}
}
</script>
<script type="module">
import AFRAME from "aframe";
// AFRAME and THREE variables are available globally, the imported aframe-master.module.min.js bundle basically does:
// import * as THREE from "three"
// window.THREE = THREE
import { TeapotGeometry } from "three/addons/geometries/TeapotGeometry.js"; // This uses the same three instance.
AFRAME.registerComponent("teapot", {
...
}
</script>
</head>The importmap example uses the above code.
"Multiple instances of Three.js being imported." warning
See Can I load A-Frame as an ES module? above.
As a library author of aframe components, be sure to configure your bundler configuration to produce a build with the three dependency declared as external if you're using any import ... from three in your code or any addons you import like import ... from three/addons/...js.
You can look at the webpack configuration in the aframe-extras repository as an example.
What order does A-Frame render objects in?
In many cases, the order in which objects is rendered doesn't matter much - most scenes will look the same whatever order the objects are rendered in - but there are a few cases where sorting is important:
- for transparent objects, it's typically better to render objects furthest to nearest (although some cases are more complex and require more sophisticated approaches). However, when the camera and/or objects are moving, this can result in undesirable visual effects when objects switch in terms of their relative distance from the camera
- for performance reasons, it's typically desirable to render objects nearest to furthest, so that GPU doesn't spend time drawing pixels that end up being drawn over.
- for AR "hider material" used to hide parts of the scene to create the appearance of occlusion by real-world objects, it's typically necessary to render these before the rest of the scene.
By default, A-Frame sorts objects as follows:
- for all objects, if
renderOrderis set on the Object3D or a Group that it is a member of, the specified order will be respected - otherwise, opaque objects are rendered furthest to nearest, and transparent objects are rendered in the order they appear in the THREE.js Object tree (in most cases, this is the same as the order they appear in the DOM)
The renderer system has a sortTransparentObjects property, which can be used to render transparent objects furthest to nearest, rather than in DOM order.
Introduction Hosting And Publishing (`docs/introduction/hosting-and-publishing.md`)
title: "Hosting & Publishing" type: introduction layout: docs parent_section: introduction order: 12
This section will show several ways to deploy, host, and publish an A-Frame
site and its assets onto the Web for the world to see.
Publishing a Site
There are many free services to deploy and host a site. We'll go over some of
the more easy or popular options, but there are certainly other options such
as AWS, Heroku, or self-hosting. An important note is that these sites should
be served with SSL/HTTPS due to a common security restriction of the browser's
WebVR API. All the options below serve with SSL/HTTPS.
Neocities
"Create your own free website. Unlimited creativity, zero ads. Neocities is
a social network of 129,100 web sites that are bringing back the lost
individual creativity of the web. We offer free web hosting and tools that
allow you to create your own web site. Join us!"
Neocities is also another free and easy way to create
and publish a site from within the browser. While it doesn't have some of the
features of Glitch, Neocities is friendly and lets us upload assets into the
project directory versus a CDN. This makes Neocities at least better at
hosting models. With Neocities, we can create and edit files. They'll then be
hosted and published for us (e.g., ngokevin.neocities.org):
Hosting Assets
We'll also go over hosting assets such as audio, textures, models, and video.
If the A-Frame site is being published alongside its assets in the same
directory (i.e., the same domain), then we don't need to worry much about
hosting assets. The A-Frame site can use relative URLs to reference the asset,
and since they're on the same domain, there is no issue with fetching that
asset. For example, if we have all your resources in the same root directory
and we publish everything via Neocities, GitHub Pages, or Surge, there will be
no issues.
Content Delivery Network (CDN)
If we're hosting assets externally, like on a CDN, then we need to take in
considerations .The primary requirement for assets is that they be served with
cross-origin resource sharing
(CORS)
enabled. This allows the A-Frame site to fetch the asset to display in the
scene. Plus, if we're using <a-assets>, we should usually setcrossorigin="anonymous" on assets such as ``, <audio>, and <video>.
There are several simple options to host assets via a CDN:
- Glitch Asset Uploader - The Glitch code editor has a
panel to upload assets and get CDN URLs in return. - imgur - For images, we could use imgur, a popular image
hosting service.
Hosting Models
Hosting models is not as simple. Models usually come as groups of files in a
folder, where the model file relatively references other files such as images.
Thus, models have to be uploaded as a single folder in the same directory. Many
of the free asset hosting services support only uploading one file a time. One
solution would be to rename all of the image paths to the CDN paths after the
images are individually uploaded, but that is tedious. There are a couple known
solutions for easily hosting models via CDN:
If publishing a site via Neocities, we can upload any number of files and
directories into the site directory:
Neocities Asset Uploader
Or we can upload assets to a GitHub repository, and use GitHub to serve the
model files.
- Go to one of our GitHub repositories.
- Click Upload files.
- Upload our assets and wait for the upload to finish.
- Type a quick message at the bottom and hit Commit changes.
- Wait for processing.
- Once finished, click on the primary asset file.
- Click on Raw.
- Then we have our asset URL hosted on GitHub. Then the asset can be
hosted and referenced via JSDelivr CDN.
Though ee can also host the model the same as we serve all of our other assets,
and let the server and CDN handle it. If we want a special place to host them,
S3 is another option.
Sharing Our Project
Once we've published our awesome project, we'll want to share it so other
people dive inside!
Creating Media
A-Frame and VR are very visual; we'll want to create videos and GIFs of our
project.
First, we want to record the screen. On OS X, record the screen using built-in
QuickTime Player's Screen Recording or OBS Studio. On Windows, we can
use OBS Studio. OBS Studio also supports streaming and compositing a
webcam image on top of the screen, which is useful to show the person using the
headset in reality (even with mixed reality).
Then, we can possibly want to trim the video. On OS X, we can use QuickTime
Player's trim tool (<cmd> + t).
To convert to a GIF with one command, use gifpardy. gifpardy uses ffmpeg
and gifsicle under the hood:
gifpardy in.mp4
gifpardy in.mp4 out.gif
gifpardy -r 320x240 --delay 8 in.mp4Alternatively, we can use GIF Brewery which has UI to trim, resize,
crop, and preview a GIF before exporting. Or capture straight to GIF using
LICECap.
Sharing Media
If you create something with A-Frame, please share it with us! If you share
your project, we'll feature it on A Week of
A-Frame for the community to see. Great channels
include:
- Twitter - Mention
@aframevror include the#webvr
hashtag. #projectschannel on Slack- WebVR Slack channel.
- /r/WebVR subreddit.
- Write a case study and tell us to feature on the A-Frame Blog.
Embedding
If we want to embed an A-Frame scene into the layout of 2D web page, we can use
the embedded component to remove fullscreen styles
and allow us to style the canvas with CSS.
Note we can only embed one scene at a time into a page. If we need multiple
scenes, we can use
[``s](https://developer.mozilla.org/docs/Web/HTML/Element/iframe).
Introduction Html And Primitives (`docs/introduction/html-and-primitives.md`)
title: "HTML & Primitives" type: introduction layout: docs order: 4 examples: []
This section will go over the concepts of A-Frame's primitive elements and
their relation to the entity-component framework. If you're looking for a guide
on using HTML and primitives, check out the Building a Basic Scene
guide.
HTML
A-Frame is based on top of HTML and the DOM using a polyfill for
Custom Elements. HTML is the building block of the Web, providing one of the
most accessible computing languages around. There are no installations or build
steps required, creating with HTML involves just text in an HTML file and
opening the HTML file in a browser. Since most of the Web was built on top of
HTML, most existing tools and libraries work with A-Frame including React,
Vue.js, Angular, d3.js, and jQuery.
If you don't have too much experience with HTML, no problem! It's fairly easy
to pick up and perhaps even easier to grasp than 2D HTML. Once you pick up the
general structure or syntax of HTML (opening tag, attributes, closing tag),
then you're good to go! Read an introduction to HTML on MDN.
Primitives
While the HTML layer looks basic, HTML and the DOM are only the outermost
abstraction layer of A-Frame. Underneath, A-Frame is an entity-component
framework for three.js that is exposed declaratively.
A-Frame provides a handful of elements such as <a-box> or <a-sky> called
primitives that wrap the entity-component pattern to make it appealing for
beginners. At the bottom of the documentation navigation sidebar, we can see
every primitive that A-Frame provides out of the box. Developers can create
their own primitives as well.
Example
Below is the Hello, WebVR example that uses a few basic primitives. A-Frame
provides primitives to create meshes, render 360° content, customize the
environment, place the camera, etc.
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>
<body>
<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>
</body>
</html>Under the Hood
Primitives act as a convenience layer (i.e., syntactic sugar) primarily for
newcomers. Keep in mind for now that primitives are <a-entity>s under the
hood that:
- Have a semantic name (e.g.,
<a-box>) - Have a preset bundle of components with default values
- Map or proxy HTML attributes to component data
Primitives are similar to prefabs in Unity. Some literature on the
entity-component-system pattern refer to them as assemblages.
They abstract the core entity-component API to:
- Pre-compose useful components together with prescribed defaults
- Act as a shorthand for complex-but-common types of entities (e.g.,
<a-sky>) - Provide a familiar interface for beginners since A-Frame takes HTML in a new direction
Under the hood, this <a-box> primitive:
<a-box color="red" width="3"></a-box>represents this entity-component form:
<a-entity geometry="primitive: box; width: 3" material="color: red"></a-entity><a-box> defaults the geometry.primitive property to box. And the
primitive maps the HTML width attribute to the underlying geometry.width
property as well as the HTML color attribute to the underlyingmaterial.color property.
Attaching Components to Primitives
Primitives are just <a-entity>s under the hood. This means primitives have
the same API as entities such as positioning, rotating, scaling, and attaching
components.
Example
Let's attach community physics components to primitives. We include the source
for Don McCurdy'saframe-physics-system and attach
the physics components via HTML attributes:
<html>
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/@c-frame/[email protected]/dist/aframe-physics-system.min.js"></script>
</head>
<body>
<a-scene physics>
<a-box position="-1 4 -3" rotation="0 45 0" color="#4CC3D9" dynamic-body></a-box>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>Registering a Primitive
We can register our own primitives (i.e., register an element) usingAFRAME.registerPrimitive(name, definition). name is a string and must contain a dash (i.e. 'a-foo'). definition is a JavaScript
object defining these properties:
| Property | Description | Example |
|---|---|---|
| defaultComponents | Object specifying default components of the primitive. The keys are the components' names and the values are the components' default data. | {geometry: {primitive: 'box'}} |
| mappings | Object specifying mapping between HTML attribute name and component property names. Whenever the HTML attribute name is updated, the primitive will update the corresponding component property. The component property is defined using a dot syntax ${componentName}.${propertyName}. |
{depth: 'geometry.depth', height: 'geometry.height', width: 'geometry.width'} |
Example
For example, below is A-Frame's registration for the <a-box> primitive:
var extendDeep = AFRAME.utils.extendDeep;
// The mesh mixin provides common material properties for creating mesh-based primitives.
// This makes the material component a default component and maps all the base material properties.
var meshMixin = AFRAME.primitives.getMeshMixin();
AFRAME.registerPrimitive('a-box', extendDeep({}, meshMixin, {
// Preset default components. These components and component properties will be attached to the entity out-of-the-box.
defaultComponents: {
geometry: {primitive: 'box'}
},
// Defined mappings from HTML attributes to component properties (using dots as delimiters).
// If we set `depth="5"` in HTML, then the primitive will automatically set `geometry="depth: 5"`.
mappings: {
depth: 'geometry.depth',
height: 'geometry.height',
width: 'geometry.width'
}
}));For example, aframe-extras package includes an<a-ocean> primitive that wraps his ocean component. Here is the definition
for <a-ocean>.
AFRAME.registerPrimitive('a-ocean', {
// Attaches the `ocean` component by default.
// Defaults the ocean to be parallel to the ground.
defaultComponents: {
ocean: {},
rotation: {x: -90, y: 0, z: 0}
},
// Maps HTML attributes to the `ocean` component's properties.
mappings: {
width: 'ocean.width',
depth: 'ocean.depth',
density: 'ocean.density',
color: 'ocean.color',
opacity: 'ocean.opacity'
}
});With the <a-ocean> primitive registered, we'd be able to create oceans using
a line of traditional HTML:
<a-ocean color="aqua" depth="100" width="100"></a-ocean>Introduction Installation (`docs/introduction/installation.md`)
title: Installation type: introduction layout: docs parent_section: introduction order: 2
This installation section offers several ways to get started with A-Frame,
although most methods don't require any actual installation since A-Frame
is primarily HTML and JavaScript.
Code Editors in the Browser
The fastest way is to start playing from within the browser.
Remix on Glitch
Glitch provides an online code editor with instant deployment and
hosting of web sites. The editor supports both front-end and back-end code as
well as multiple files and directories. Glitch lets us remix (i.e., copy)
existing projects and make them our own and instantly host and deploy changes
for everyone to see.
Hit Remix on this A-Frame project, mess with the HTML inindex.html, and see your site published live on each change! The base A-Frame
Glitch, for example, is published at aframe.glitch.me, but we will provide your
own custom URL name.
Below are a few other A-Frame Glitches for starters:
- aincraft - Minecraft demo.
- 360-gallery - 360° image gallery.
- community-components - Showcase of various community components.
- aframe-vaporwave - Retro-futuristic scene.
Other Code Editors
Below are a couple of A-Frame starter kits on other browser-based code
editors. Both support remixing or forking:
Local Development
Use a Local Server
For the options below, we should develop projects using a local server so that
files are properly served. Options of local servers include:
- Run
npm i five-server@latest && five-server --port=8000in a terminal
in the same directory as your HTML file. - Run
python3 -m http.serverin a terminal in the same directory as your
HTML file.
Once we are running our server, we can open our project in the browser using
the local URL and port which the server is running on (e.g.,http://localhost:8000). Try not to open the project using the file://
protocol which does not provide a domain; absolute and relative URLs may not
work.
Include the JS Build
To include A-Frame in an HTML file, we drop a <script> tag pointing to the
CDN build:
<head>
<script src="https://aframe.io/releases/1.8.0/aframe.min.js"></script>
</head>If we want to serve it ourselves, we can download the JS build:
Production Version 1.8.0 Minified
Development Version 1.8.0 Uncompressed with Source Maps
Install from npm
We can also install A-Frame through npm:
$ npm install aframeThen we can bundle A-Frame into our application. For example, with Webpack or
Vite:
import AFRAME from 'aframe';If you use npm, you can use angle, a command line interface for
A-Frame. angle can initialize a scene template with a single command:
npm install angle && angle initsceneCordova Development
A-Frame is compatible with Cordova apps. Currently, network access is required as A-Frame and its dependencies load assets from CDN sources.
Cordova A-Frame Showcase App (demo)
Installation
Install the cordova-plugin-xhr-local-file plugin. This is needed because
Cordova runs from file://, and XHR requests to local file:// assets (JSON fonts, 3D models, etc) will fail without this plugin.
cordova plugin add cordova-plugin-xhr-local-fileIn your index.html, adjust as follows:
<head>
<meta
http-equiv="Content-Security-Policy"
content="
default-src
'self'
data:
gap:
https://ssl.gstatic.com
'unsafe-eval'
https://cdn.aframe.io <-- required
https://fonts.googleapis.com <-- required
https://cdn.jsdelivr.net <-- your choice, see below
;
style-src
'self'
'unsafe-inline'
;
media-src *;
img-src
'self'
data: <-- required
content: <-- required
blob: <-- required
;
"
/>
...
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-master.min.js"></script>
<script id='my-scene' type="text/html">
...your scene goes here...
</script>
<script>
document.addEventListener('deviceready', function() {
// After the 'deviceready' event, Cordova is ready for you to render your A-Frame scene.
document.getElementById('scene-root').innerHTML = document.getElementById('my-scene').innerHTML
})
</script>
</head>
<body>
<div id='scene-root'></div>
...
</body>Discussion
deviceready
The most important difference between a browser environment and a Cordova environment is waiting for the deviceready event
before rendering your scene.
The sample above shows a pure DOM+JS approach, but you can also use a framework like React:
document.addEventListener('deviceready', () => {
ReactDOM.render(<Root />, document.getElementById('root'))
})Layout
Depending on your target device, you may find that A-Frame's default CSS causes certain buttons and controls to appear out of
position or too close to the edge of the phone screen. Supply your own CSS overrides to adjust positioning to fit
the target device.
Introduction Interactions And Controllers (`docs/introduction/interactions-and-controllers.md`)
title: Interactions & Controllers type: introduction layout: docs parent_section: introduction order: 9
There is no one true way for adding interactions due to variety of platforms,
devices, input methods that A-Frame can support. On top of that, VR interaction
is open-ended. Unlike the 2D Web where we only have to worry about mouse and
touch input,
we can do anything in VR: grab, throw, rub, flip, poke, stretch, press, etc.
Going further, mixed reality, trackers, and custom controllers provide even
more creativity in interaction!
What we can do in this section is go over existing components for common
interactions. And we can show how these input-based and interaction-based
components are built in order to give us the knowledge to build our own
interactions. In a sense, learn how to fish rather than being given a fish.
Events
In the 2D Web, input and interactions are handled through browser
events (e.g., click, mouseenter, mouseleave, touchstart,touchend). Whenever an input-based event happens, the browser will emit
an event that we can listen to and handle withElement.addEventListener:
// `click` event emitted by browser on mouse click.
document.querySelector('p').addEventListener('click', function (evt) {
console.log('This 2D element was clicked!');
});Just like the 2D Web, A-Frame relies on events and event listeners for
interactivity and dynamicity. However because A-Frame is a JavaScript framework
and everything is done in WebGL, A-Frame's events are synthetic custom
events that can be emitted by any component describing any event:
// `collide` event emitted by a component such as some collider or physics component.
document.querySelector('a-entity').addEventListener('collide', function (evt) {
console.log('This A-Frame entity collided with another entity!');
});A common misbelief is that we can add a click event listener to an A-Frame
entity and expect it to work by directly clicking on the entity with our mouse.
In WebGL, we must provide the input and interaction that provides such click
events. For example, A-Frame's cursor component creates a synthetic click
event on gaze using a raycaster. Or as another example, Mayo Tobita's
mouse-cursor component creates a synthetic click event when
clicking directly on the entity using a raycaster.
Gaze-Based Interactions with cursor Component
Remix this cursor example on Glitch.
We'll first go over gaze-based interactions. Gaze-based interactions rely on
rotating our heads and looking at objects to interact with them. This type of
interaction is for headsets without a controller. Even with a rotation-only
controller (Oculus Go), the interaction is still similar. Since A-Frame
provides mouse-drag controls by default, gaze-based can sort of be used on
desktop to preview the interaction by dragging the camera rotation.
To add gaze-based interaction, we need to add or implement a component. A-Frame
comes with a cursor component that provides gaze-based interaction if
attached to the camera:
- Explicitly define
<a-camera>entity.
Previously, A-Frame was providing the default camera. - Add
<a-cursor>entity as a child element underneath the camera entity. - Optionally, configure the raycaster used by the cursor.
<a-scene>
<a-camera>
<a-cursor></a-cursor>
</a-camera>
</a-scene>Handling Events with event-set Component
Remix this event set component example on Glitch.
Now let's handle the events that the cursor component provides. The cursor
component emits synthetic events like click, mouseenter, mouseleave,mousedown, mouseup, and fusing. We named them similarly to the browser's
native events to be familiar for newcomers, but again note they are synthetic
events.
For basic event handler where we listen to an event and set a property in
response, we can use the event-set component. The event-set
component makes basic event handlers declarative. The API for the event-set
component looks like:
<a-entity event-set__${id}="_event: ${eventName}; ${someProperty}: ${toValue}">The __${id} piece lets us attach multiple event-set components on the sam