WebVRCreating immersive WebVR experiences with HTML

Virtual reality development may sound daunting, but it can be a lot easier than you think. We’ve put together a breeze tutorial to help you create your first VR experience in just a few lines of code.

If you ever thought about getting into the exciting and growing world of virtual reality development, you may be aware it can seem like a lot of work at first. Just to get started you need to learn a game engine, the programming languages that come with it, a 3D modeling tool, and a variety of VR UX/UI concepts. Not to mention that you would need to acquire one of the costly VR headsets! Fortunately, as VR gains popularity it is becoming more accessible for both developers and consumers.

Thanks to new technologies such as WebVR, we can easily build web-based VR experiences just like we would with any other web application… with HTML! Exciting, right? Now, how can you get started, you may be asking. In this tutorial, you will be introduced to A-Frame, one of the most popular web frameworks for building virtual reality experiences. Prior knowledge of HTML and JS is recommended, but you should be able to follow along regardless. We will be using A-Frame to build our own little VR experience where we can travel the world (or at least get that impression), which everyone with access to the internet and a VR headset will be able to try out. If you are curious to see what it is going to look like, go take a look at it below!

image.png Check it out

Getting started

As mentioned before, A-Frame is based on HTML. This means that we are starting with nothing but an HTML file. In this tutorial, we will be serving the project files locally in our own directory, but you could also make things easier by using an online code editor like Glitch for developing, running, and publishing your WebVR application. In either case, we will need an index.html file where we can get started. This file is where we will manage our A-Frame scene, as well as our dependencies.

Just like with other HTML applications we can import dependencies by including them in our head tag. Of course, our first dependency should be A-Frame itself, so the compiler can actually understand the incoming magic.

<head>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
 </head>

By importing A-Frame we gain access to its tags. Even though we are working with HTML, we will have to say goodbye to our beloved div tags and say hello to a-entity and his friends. The A-Frame compiler only understands the latter, so our application starts with the a-scene tag. This will act as the root where everything in our scene will occur (hence the name).

Our index.html should end up looking something like this. If we try to compile it we will already see an empty 3D scene with VR controls, and we barely even wrote anything! A-Frame takes care of all of this, and we do not have to worry about any of the complicated stuff.

The official A-Frame documentation is a great resource to dive into if you want to learn more about the framework, from fundamentals to more advanced topics.

<html>
  <head>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
 
    </a-scene>
  </body>
</html>

Populating our scene

Camera

Our scene is looking a little empty, so let’s go and add some objects. Since we want to create an immersive experience, a good place to start would be to add our camera object.

<a-entity camera position="0 1.6 0" look-controls></a-entity>

This object will represent the user’s VR headset, so this is where we might want to configure our controls. The a-scene tag adds a camera by default, but we want our own reference to it, so we can disable WASD-controls (we do not want this in this application) and do other things with it.

As you can see, the camera is a basic a-entity object, but by adding custom attributes we can tell the compiler what it should do with it. You will also notice the position attribute; this defines where the object should be positioned in its parent object (the scene in this case). A-Frame loves meters as much as we do, so we can define the three dimensions (XYZ) in meters! So in this case, “0 1.6 0” just means we want the camera to start in the center, at a height (Y) of 1.6 meters.

Importing 3D models

We added our first object to our scene, but it is still empty. A-Frame offers us a variety of spheres we can add to our scene, but if we want an immersive experience we have to dream bigger than that. Let’s go ahead and add our very own sports car (after all, VR allows us to live in another reality). This part may put you off if you have no prior experience with 3D modelling, but there are actually many great 3D models out there that are completely free for you to use in your projects (just make sure it has a license that allows you to do so). For our car, we are using this free CC0 model, with a license that allows us to use and modify it however we want.

Say you found a cool model, you can download it as a .obj file... Even better would be to have a .gltf or .glb file, since these offer more features such as animations and hierarchical objects. For a simple object like this, .obj should do fine, but since I made some modifications to the model I exported it as a .glb file.

Now we want to import the car into our scene. To do this we will first need a folder we can put our assets in (.obj files work a little different though; take a look at the docs for information). After adding these we need to create a reference to these assets in our *index.html *file:

        <a-assets>
            <a-asset-item id="car" src="assets/car.glb"></a-asset-item>
        </a-assets>

We created an a-asset-item with the car identifier, which contains a reference to our car model. To put this model in the scene all that is left for us to do is add an a-entity and link it to our asset reference.

Having the car spawn in our own body is not the experience we are going for here though, so the position, rotation, and scale properties have been adjusted to put it where we want to.

Tip: To mess around with objects and properties you can open the inspector (<ctrl> + <alt> + i)!

        <a-entity
            id="car"
            gltf-model="#car"
            position="-2 0 -2"
            rotation="0 170 0"
            scale="1.2 1.2 1.2"
        ></a-entity>

Making things happen

Changing the environment

So we got a cool car we can travel the world with… but we have not created a world yet! Let’s fix that and change the environment. Now, I am not talking about becoming carbon-neutral here, but about the environment of our scene. Luckily for us, there is a component for this. Aside from all the things A-Frame offers out of the box, the community is creating a lot of interesting components to add to your project. In our demo, we will be using aframe-environment-component, which allows us to add a bunch of basic environments to the scene.

Just like we had to import A-Frame earlier, we now need to import this component:

<script 
src="https://unpkg.com/aframe-environment-component@1.3.1/dist/aframe-environment-component.min.js"></script>

With this component, it is now possible to add an environment property to an a-entity object. It would be nice if we start in a forest, so we initialize it with the forest preset:

<a-entity id="environment" environment="preset: forest"></a-entity>

We want to be able to change the environment though, so we will have to do this programmatically. That means it is time to create a JavaScript file and import it into our HTML file!

<script src="main.js"></script>

Your HTML file should now look something like this:

<html>
  <head>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-environment-component@1.3.1/dist/aframe-environment-component.min.js"></script>
    <script src="main.js"></script>
  </head>
  <body>
    <a-scene>
        <a-assets>
            <a-asset-item id="car" src="assets/car.glb"></a-asset-item>
        </a-assets>
 
        <a-entity id="environment" environment="preset: forest"></a-entity>
 
        <a-entity
            id="car"
            gltf-model="#car"
            position="-2 0 -2"
            rotation="0 170 0"
            scale="1.2 1.2 1.2"
        ></a-entity>
 
        <a-entity camera look-controls position="0 1.6 0"></a-entity>
    </a-scene>
  </body>
</html>

In the JavaScript file we can get elements from the DOM like you may be used to, which means we can manipulate A-Frame objects and do other A-Frame things just as if you are developing a basic website. This is what the functionality for changing the environment looks like:

let environmentEl;
 
const presets = ['forest', 'egypt', 'japan', 'dream', 'volcano', 'goldmine', 'tron', 'starry'];
let activePreset = 'forest';
 
window.onload = () => {
    environmentEl = document.getElementById('environment');
}
 
const nextPreset = () => {
    const nextPreset = presets[presets.indexOf(activePreset) + 1] || presets[0];
 
    environmentEl.setAttribute('environment', `preset:${ nextPreset }`);
 
    activePreset = nextPreset;
}

Gaze controls

We want to be able to cycle through our environments by interacting with our car. But how can we achieve this? If we would like our experience to be accessible to mobile VR users, we need to come up with a way to do this without requiring a controller. So why not just look at the car to interact with it? VR is different from traditional games, which means that we have to use different UI/UX techniques to make the most out of its potential, but also to make our experience accessible. The technique we use here is called gaze control.

To make gaze control work we need a way to detect that the user is looking at the car. A-Frame has a built-in functionality for this, meaning that we can add a cursor to our camera and add a fuse to it (think of it as a laser strapped to the user’s head). Our new gaze-based cursor will now trigger a click event when the user looks at an object for a set amount of time.

        <a-entity camera look-controls position="0 1.6 0">
            <a-entity
                id="cursor"
                cursor="fuse: true; fuseTimeout: 500"
                position="0 0 -1"
                material="color: black"
                geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
            ></a-entity>
        </a-entity>

But we only want to trigger an event whenever we look at the car, not when we are just trying to enjoy the view. For this, we can create a custom component. A-Frame allows us to create components in JavaScript which we can configure to assign appearance, behavior, and functionality to objects.

We want the car to show a new environment when we trigger a click event on it, so we will create a component and name it cursor-listener. When this component is initialized, it will start listening for a click event; this will execute our nextPreset() function. To show the user that they are looking at the car, we will also add listeners that will change the cursor color based on this fact:

AFRAME.registerComponent('cursor-listener', {
    init: () => {
        const cursor = document.getElementById("cursor");
 
        this.el.addEventListener('click', () => {
            nextPreset();
        });
 
        this.el.addEventListener("mouseenter", () => {
            cursor.setAttribute("material", "color: white");
        });
 
        this.el.addEventListener("mouseleave", () => {
            cursor.setAttribute("material", "color: black");
        });
    }
});

Now, all we need to do is let the compiler know our car object uses the cursor-listener component:

        <a-entity
            id="car"
            gltf-model="#car"
		...
            cursor-listener
        ></a-entity>

Now it’s up to you

In a matter of a few steps and a small amount of code, we’ve created our very own WebVR experience. We hope that this first ride through the world of virtual reality in our Miyagami car has shown you how easy and fun it can be to get started with VR development. Now is the time for you to continue this journey on your own and explore all that A-Frame has to offer. If you are interested in learning more, we find that A-Frame’s official documentation is loaded with useful information. All that’s left to say is. Bon voyage!

Source code can be found on GitHub

Jeroen Bol

Full-Stack Developer

Accelerate your digital transformation.

With a strong innovation and technology-focused mindset, we explore your problems and come up with the best tailor-made solution.

Contact us