Build your own building blocks for WebVR using A-frame

If you’re interested in building virtual reality experiences for the web, then one of the most approachable ways to go about it is to use a framework like A-Frame. A-Frame makes it really easy to get started with WebVR, it’s an active, well-managed project with a vibrant and growing community, and it’s very fun to use and play around with.

What is A-Frame?

A-Frame is a web framework designed to make WebVR content creation easier, faster, and more accessible. A-frame was started by Mozilla, and has been around since mid-2015. From theĀ A-frame documentation:

A-Frame lets you build scenes with just HTML while having unlimited access to JavaScript, three.js, and all existing Web APIs. A-Frame uses an entity-component-system pattern that promotes composition and extensibility. It is free and open source with a welcoming community and a thriving ecosystem of tools and components.

If you’ve never used it at all, here’s a super-minimal “Hello World” example. It uses a declarative HTML-type syntax as seen below:

See the Pen A-Frame super minimal example by Mark Lee (@leemark) on CodePen.

At the top there’s a link to the A-Frame script, then the <a-scene> element encompasses everything in the WebVR scene, the same way that the <body> element encompasses everything the uses sees in a web page.

Inside the scene there is an <a-box> element which makes–you guessed it–a box appear in the scene. The box has attributes that describe how it should appear, including its position within the scene, its rotation, and its color.

The <a-box> element is a built-in primitive provided by A-Frame. The rest of this post is going to focus on how to build your own reusable components that don’t already exist in A-Frame, how to build your own building blocks in a sense. If this is truly your first time using A-Frame I recommend that you play around with it a little bit to get a feel for it.

Here’s a starter CodePen you can fork and play with, or a Glitch project if you prefer (p.s. both CodePen and Glitch are awesome, and if you haven’t used them you should!). Then check out the introductory parts of the documentation especially the Introduction and Getting Started sections, and maybe even go through the Building a Basic Scene guide. Also here’s a great YouTube playlist of A-Frame tutorials.

Build Your Own Components

Once you’re up to speed with building basic scenes using built-in components and primitives like boxes, spheres, cylinders, etc. you will reach a point where you might want to build your own custom components that you can use in your projects, and even share for others in the community to use.

For a simple example, here’s a “clock” component showing the current time. Next we’ll walk through how it’s built, step-by-step.

See the Pen a-frame clock by Mark Lee (@leemark) on CodePen.


hint: click and drag around

Register Your Component

The first thing you need to do is let A-Frame know about your component by registering it. You do this with AFRAME.registerComponent(), as seen below:

AFRAME.registerComponent('clock', {
    // component code goes here 
});

AFRAME.registerComponent() takes two arguments, the first will be the name of your component, and the second is an object that represents your component itself, containing all of its properties and methods.

Component Lifecycle Methods

A-Frame provides a number of lifecycle methods for your component. These are built-in methods that will automatically get called at certain times, like when it is first initialized, or when its properties are changed. This is where you make your component “do stuff”. In this case we’ll be using the init() and tick() lifecycle methods.

  AFRAME.registerComponent('clock', {
    init: function () {
      // this code runs when the component is first initialized  
    },
    tick: function(){
      // this code gets run on each render loop
      // so typically 30 to 60+ times per second
    }  
  });

First, we’ll take a look at init().

Hello World

Here’s how you can create a text element and add it to the scene (add it to the DOM).

See the Pen a-frame hello world component by Mark Lee (@leemark) on CodePen.

One great thing about A-Frame is, if you are a web developer and are used to working with the DOM, everything pretty much works the way you would expect. For example, creating an element, appending it to another element, and adding a text value, as seen below:

// create a new <a-text> element 
this.clockEl = document.createElement('a-text');
// append it to the component's main element
this.el.appendChild(this.clockEl);
// now set the 'value' attribute 
this.clockEl.setAttribute('value', 'Hello World!');

You can now add the Hello World text to the scene with <a-entity clock></a-entity>. Congratulations, you’ve now built your first component!

In the Codepen above I have also given the entity a position since otherwise it would be at the origin (0, 0, 0) position in the same place as the camera, which would make it hard to see.

Passing in properties

What’s the fun in having a reusable component if you’re only going to have one instance of it, or if all instances will be exactly the same? Let’s make it so that we can pass in some properties and customize each instance of the component.

To do that we will use the schema. The schema is an object that defines the properties for your component. In other words, the schema tells A-Frame what properties to expect to be passed in and what data types (or “property types”) those properties will be. It also lets you define default values in case any of those properties aren’t passed in.

schema: {
  position: {type: 'vec3', default: {x: -.75, y: 1.75, z: -1.75}},
  color: {type: 'color', default: '#0f0'},
  font: {type: 'string', default: 'monoid'}
}

This schema is saying that we can expect a position, a color, and a font to potentially be passed in, that the types of these properties will be vec3, color, and string respectively, and then setting a default value for each.

Once they are defined in the schema, we can access each of these properties via the component’s data object, allowing us to easily grab these values and set them for our component instance using setAttribute().

init: function () {
  this.clockEl = document.createElement('a-text');
  this.el.appendChild(this.clockEl); 
//get the position that was passed in, and set it on this instance
  this.clockEl.setAttribute('position', this.data.position);
//get the color that was passed in, and set it on this instance
  this.clockEl.setAttribute('color', this.data.color);
//get the font that was passed in, and set it on this instance
  this.clockEl.setAttribute('font', this.data.font);
  this.clockEl.setAttribute('value', 'Hello World!');
}

Now when you add the component to the A-Frame scene, you will pass in the position, color, and font like so:
<a-entity clock="font: sourcecodepro; color: #191; position: -1 1.5 -.75"></a-entity>

You can see how this all comes together in the Codepen:

See the Pen a-frame hello world component, with schema by Mark Lee (@leemark) on CodePen.

Make time for making time

We’re actually getting pretty close here, now that you know how to register your component, build your component by adding elements to the DOM, use the built-in lifecycle methods, and use schema to define and pass in properties.

Two things still need to be done in order to turn this Hello World example into a working clock component: we have to get the current time, and then update it regularly. First let’s add a getTime() method to our component that will get a JavaScript Date object and return it as a formatted time string.

getTime: function() {
    var d = new Date();
    return d.toLocaleTimeString();
}     

Then we want to use the tick() lifecycle method to call getTime periodically and update the text element accordingly with the current time (tick() will be called every render loop, many times per second).

tick: function(){
    this.clockEl.setAttribute('value', this.getTime());
}

With those last two pieces in place, we’ve got a fully working and reusable a-frame clock component, which you can instantiate like so:

See the Pen a-frame clock by Mark Lee (@leemark) on CodePen.

“But wait,” you say, “what’s all this <a-entity> nonsense!? I want a proper <a-clock> element!”

In that case, what you want to do is register your brand-new component as a primitive.

Extra Credit: Registering a new Primitive

Primitives extend the <a-entity> as a new custom element, allowing us to instantiate our clock using <a-clock>. Here’s how it works:

AFRAME.registerPrimitive('a-clock', {
  // Attaches the 'clock' component by default
  defaultComponents: {
    clock: {}
  },
  // Maps HTML attributes to the component's properties
  mappings: {
    position: 'clock.position',
    font: 'clock.font',
    color: 'clock.color'
  }
});

And here’s the final Codepen, with everything put together, and three <a-clock>s added to the A-Frame scene:

See the Pen a-frame clock as primitive by Mark Lee (@leemark) on CodePen.

Resources & furtherĀ reading