title: Best Practices type: introduction layout: docs parent_section: introduction
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 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:
tick
handlers to hook into the global render loop. Use utilities such as AFRAME.utils.throttleTick
to limit the number of times the tick
handler is run if appropriate.a-sky
to define a solid color as the scene background. This prevents the creation of unnecessary geometry.position
, rotation
, scale
, and visible
using at the three.js level (el.object3D.position
, el.object3D.rotation
, el.object3D.scale
, el.object3D.visible
) to avoid overhead on .setAttribute
..setAttribute
and animate JS values directly. For example, instead of material.opacity
, animate components.material.material.opacity
.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.
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 like utils.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:
tick
HandlersIn 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.
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:
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.