Handling Events

Tutorial #2 – Handling Events

by Michael Colonna

This tutorial assumes that you've completed Tutorial #1 – First AR.js App. If you don't have a Glitch account, it might be a good idea to make one.

For this tutorial, we'll be expanding on our first AR.js app by adding interactions with entities in the scene. In particular, we'll be making it so we can rotate our scene object by dragging our cursor across the screen. Unfortunately, I could only get this to work in the browser for now, as Touch events are a bit trickier than Mouse events (but it's definitely doable and I encourage you to try it out after the tutorial!).

Getting Started

Let's start with our code from the previous tutorial. If you didn't do the first tutorial, this is the code we're working with:

<!DOCTYPE html>

<html lang="en">

  <head>

    <title>Hello!</title>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    

    <!-- import the webpage's stylesheet -->

    <link rel="stylesheet" href="/style.css">

    

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

    <script src="https://rawgit.com/jeromeetienne/AR.js/master/aframe/build/aframe-ar.min.js"></script>

  </head>  

  <body>

    <a-scene embedded arjs>

      <a-marker preset="hiro">

        <a-box position="0 0.5 0" material="color: yellow;"></a-box>

      </a-marker>

      <a-entity camera></a-entity>

    </a-scene>

  </body>

</html>

Add the following components to the <a-marker> tag:

<a-marker markerhandler emitevents="true" cursor="rayOrigin: mouse" preset="hiro" id="marker">

Now add the following components to the <a-box> tag:

<a-box id="box" position="0 0.5 0" rotation="0 0 0" material="color: yellow;"></a-box>

The "emitevents" and "cursor" components allow the <a-marker> object to send out events (e.g. mousedown, keyup, double click) with rays originating from the mouse. The "id" and "rotation" components will come in handy when we write our event listeners.

Writing the Event Listeners

Create a new file called "events.js" and copy and paste this code into it:

AFRAME.registerComponent('markerhandler', {

  

  init: function() {

    const marker = document.querySelector('#marker');

    const box = document.querySelector('#box');

    

    let selected = false;

    let origX = 0.0;

    let endX = 0.0;

    let deltaX = 0.0;

    const rotationSpeed = 0.5;


    marker.addEventListener('mousedown', function(ev, target) {

      const intersectedElement = ev && ev.detail && ev.detail.intersectedEl;

      if (box && intersectedElement === box) {

        selected = true;

        origX = ev.clientX;

      }

      

      function onMouseMove(ev) {

        if (selected) {

          endX = ev.clientX;

          if (origX && endX) {

            deltaX = origX - endX;


            const rotation = box.getAttribute('rotation');

            const newY = rotation.y - deltaX * rotationSpeed; 

            box.setAttribute('rotation', {x: rotation.x, y: newY, z: rotation.z});

          }

          origX = endX;

        }

      }

      

      document.addEventListener('mousemove', onMouseMove);

      

      marker.addEventListener('mouseup', function(ev, target) {

        selected = false;

        document.removeEventListener('mousemove', onMouseMove);

        marker.onmouseup = null;

      });

    }); 

  }

});

What's going on here?

Let's break this code down.

At the very top we have:

AFRAME.registerComponent('markerhandler', {

This tells A-Frame to create a component called "markerhandler" with the following code snippet inside. Remember when we added "markerhandler" to <a-marker>? This is where that came from.

init: function() {

All A-Frame components begin with an init() function.

    const marker = document.querySelector('#marker');

    const box = document.querySelector('#box');

    

    let selected = false;

    let origX = 0.0;

    let endX = 0.0;

    let deltaX = 0.0;

    const rotationSpeed = 0.5;

Here, we're first grabbing our <a-marker> and <a-box> objects from the DOM using their IDs. Then, we declare some variables that will be useful for our drag-to-rotate code. The code works by taking a starting point and an end point, calculating the deltaX between the two, and then changing the box's y-rotation by that amount (scaled by rotationSpeed).

    marker.addEventListener('mousedown', function(ev, target) {

      const intersectedElement = ev && ev.detail && ev.detail.intersectedEl;

      if (box && intersectedElement === box) {

        selected = true;

        origX = ev.clientX;

      }

We're now adding an event listener to the marker object, specifically listening for the 'mousedown' event, which is fired whenever a cursor is depressed. The first thing the event listener does is grab the "intersectedElement" by using the event variable "ev". This is determined by casting a ray from the mouse location. If the intersectedElement === box, then we set "selected" to true and note the starting position of our drag.

      function onMouseMove(ev) {

        if (selected) {

          endX = ev.clientX;

          if (origX && endX) {

            deltaX = origX - endX;


            const rotation = box.getAttribute('rotation');

            const newY = rotation.y - deltaX * rotationSpeed; 

            box.setAttribute('rotation', {x: rotation.x, y: newY, z: rotation.z});

          }

          origX = endX;

        }

      }

      

      document.addEventListener('mousemove', onMouseMove);

Now, we define an event listener for the 'mousemove' event. Roughly, this code calculates the difference between the starting position and "end" (or current) position and subtracts this difference from the box's y-rotation attribute. It then sets the box's rotation attribute to a new rotation object with the new calculated value. Note that this event listener is added to the document itself (rather than the marker) as a mouse could move all over the screen and off the object.

      marker.addEventListener('mouseup', function(ev, target) {

        selected = false;

        document.removeEventListener('mousemove', onMouseMove);

        marker.onmouseup = null;

      });

Finally, when the mouse is let go, a 'mouseup' event is triggered. This removes all extraneous event listeners from the document and marker and changes the drag boolean to false.

Run the app!

Add the following <script> to your HTML header:

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

Now, navigate to your web app! You should be able to use the mouse to drag and rotate your object. Pretty neat!

With event handling, you can add all sorts of interactions to objects in your A-Frame scene. Go wild!