Intermediate Tutorial


DOM Event Listeners


20 minutes

Posted on: February 12, 2018

Learn Sumerian
DOM Event Listeners

Tags

dom events
event listeners
interactivity
js
spinning

In this tutorial you will learn about:

Dom events
interactivity

This tutorial helps you create an interactive scene that responds to a user’s click, drag, or touch.

You can handle user input (from mouse, keyboard, controllers, etc.) by using JavaScript-style event listeners. Because the engine is written in JavaScript, all the available functionality for interacting with the DOM is already provided for you. You can write any JavaScript code in Amazon Sumerian scripts, including scripts that manipulate or interact with the DOM. This tutorial focuses on the standard, unembellished DOM events. In this tutorial, you will write a script that allows a user to spin an entity using the mouse.

You’ll learn about:

  • DOM events
  • Event listeners
  • Interactivity
  • JavaScript
  • Spinning

This is the scene we will build in this tutorial. With your mouse, click the Box and move your cursor to spin the Box. For best results, use the latest version of your browser..

Prerequisites

Before you begin, you should have completed the following tasks and tutorials:

Step 1: Scene Setup

  1. From the Dashboard, create a new scene using the Default Lighting scene template.

  2. From the Create Entity menu, add any 3D entity to your scene. We will use a Box. Note: It’s best to not use a Sphere because it’s difficult to see a Sphere spinning.

  3. Change the color of the Box. We will use Amazon blue: Hex: 232F3E.

  4. Add a Script component to the Box and choose a Custom script. Name the script “Spinner”.

  5. From the Create Entity menu, add a Fixed Cam.

  6. With the Fixed Cam selected, check the Main Camera and Follow Editor Camera checkboxes. Make sure your camera is viewing the Box.

Because the final result is to spin an entity (Box) with the the mouse, using a Fixed Camera as your primary camera will allow you to spin the Box without orbiting the camera at the same time.

Step 2: Understand JavaScript Event Listeners

You can attach event handlers to elements (such as divs or canvases ), to the document or to the window object. You add event listeners by using certain event names and callback functions, which execute when the selected event triggers. The following code demonstrates how to add a simple mousedown event listener to the window object:

window.addEventListener('mousedown', function(evt) {
    console.log('Mouse down event triggered!', evt);
});

The evt object contains properties of the event, for example, the coordinates where the mousedown event occurred. Each type of event has its own set of properties and can be attached to a certain set of elements. Refer to the MDN for full details, or use the console, as in the preceding script, to print the evt object.

Step 3: Add and Remove Event Listeners

When using DOM event listener scripts inside Amazon Sumerian, keep the following in mind:

  • Scripts have a setup function and a cleanup function, which run when pressing play or stop, respectively. That makes the setup function perfect for adding event listeners, but it also means that we need to remove them in cleanup. Otherwise, the added event listeners will persist and we’ll get another set every time we play the scene. When removing event listeners, one needs a stored handle for them. That means we wouldn’t be able to remove the simple mousedown event from the window in the preceding example because we used an anonymous function to add it.

  • Having a good script structure makes it easy to customize and work with the event listeners.

  1. Open the script editor. Because we’ll need the script handles in both the setup and in the cleanup functions, the script-wide ctx object is a good candidate to use for storage. Start by adding some event listeners and storing them as properties on the ctx object. Then use a simple loop to attach them to the canvas, and another one to clean them up.

     var setup = function(args, ctx) {
    
         ctx.evtListeners = {
             mousedown: function(evt) {
                 console.log('mousedown!', evt);
             },
             mouseup: function(evt) {
                 console.log('mouseup!', evt);
             }
         };
    
         for (var handle in ctx.evtListeners) {
             // The canvas element can be reached through ctx.domElement
             ctx.domElement.addEventListener(handle, ctx.evtListeners[handle]);
         }
    
     };
    
     var cleanup = function(args, ctx) {
         for (var handle in ctx.evtListeners) {
             ctx.domElement.removeEventListener(handle, ctx.evtListeners[handle]);
         }
     };
    
  2. If you add the preceding functions to the script and run it, you can play the scene, and check out the triggered mouse events.

Step 4: Spinning with Speed

We’ll combine the mousedown, mouseup, and mousemove events and their touch counterparts to let the user spin the 3D object with some simple inertia. The idea is to track the X position where the user started to drag, and use the dragging distance to add some rotation speed. For this, we’ll need a few variables.

  1. Add the following to the top of the setup function. Their use will soon become apparent.

     ctx.dragging=false;ctx.velocity=0;ctx.lastX=0;ctx.currentX=0;
    
  2. We also need two customizable parameters. The first one, sensitivity, controls the speed in relation to the dragging distance. The second, damping, ensures that the Box doesn’t spin forever. Now add the parameters.

     var parameters = [
         {
             key: 'sensitivity',
             name: 'Sensitivity',
             type: 'float',
             control: 'slider',
             min: 0.01,
             max: 1.00,
             default: 0.05
         },
         {
             key: 'damping',
             name: 'Damping',
             type: 'float',
             control: 'slider',
             min: 0,
             max: 1,
             default: 0.05
         }
         ];
    
  3. Write the functions that will do the actual work.

     var startDrag = function(ctx, x) {
         ctx.dragging = true;
         ctx.currentX = x;
     };
    
     var stopDrag = function(ctx) {
         ctx.dragging = false;
     };
    
     var drag = function(ctx, x) {
         if (ctx.dragging) {
             // Record the x position from the last update
             ctx.lastX = ctx.currentX;
             // Update the current x position
             ctx.currentX = x;
             // Use the dragged distance since last update to add velocity
             ctx.velocity += ctx.currentX - ctx.lastX;
         }
     };
    
     var update = function(args, ctx) {
         // Use the accumulated velocity to add rotation around the y axis.
         // Apply the elapsed time to to be frame rate independent.
         ctx.entity.addRotation(0, args.sensitivity * ctx.velocity * ctx.world.tpf, 0);
         // Apply damping
         ctx.velocity *= (1-args.damping);
     };
    

    As you can see, the dragging functions roughly correspond to mousedown, mouseup, and mousemove. We don’t write them directly in the mouse event callbacks because we want to use them for touches later. These functions are concerned only with the ctx object and the event X coordinate. The update function is one of the standard, predefined script functions. It runs once every frame. Here, it doesn’t care about the dragging functionality but only uses the velocity to rotate the entity.

  4. Now, let’s hook up the functions. Because we wrote quality functions, it’s fairly easy to add touch events. We just need to access the X coordinate in a slightly different way. The event listener object in the setup function will look like this.

ctx.evtListeners = {
    mousedown: function(evt) {
        startDrag(ctx, evt.clientX);
    },
    mouseup: function() {
        stopDrag(ctx);
    },
    mouseout: function() {
        stopDrag(ctx);
    },
    mousemove: function(evt) {
        drag(ctx, evt.clientX);
    },
    touchstart: function(evt) {
        startDrag(ctx, evt.touches[0].clientX);
    },
    touchend: function() {
        stopDrag(ctx);
    },
    touchmove: function(evt) {
        drag(ctx, evt.touches[0].clientX);
    }
};

Note: Loops, which add and remove the listeners, don’t need to be changed. The touch events have one extra layer before we get to the coordinate. This is because the touches store information for multiple touches, though one is enough for this exercise. If you want to test the touch functionality, check out your browser’s emulation capabilities.

Complete Script

That’s all the code we need to rebuild the scene at the top of the page. If you have problems with getting the scene to work properly, the following is the complete script.

var setup = function(args, ctx) {
    ctx.dragging = false;
    ctx.velocity = 0;
    ctx.lastX = 0;
    ctx.currentX = 0;

    ctx.evtListeners = {
        mousedown: function(evt) { startDrag(ctx, evt.clientX);    },
        mouseup: function() { stopDrag(ctx); },
        mouseout: function() { stopDrag(ctx); },
        mousemove: function(evt) { drag(ctx, evt.clientX); },
        touchstart: function(evt) { startDrag(ctx, evt.touches[0].clientX); },
        touchend: function() { stopDrag(ctx); },
        touchmove: function(evt) { drag(ctx, evt.touches[0].clientX);}
    };

    for (var l in ctx.evtListeners) {
        ctx.domElement.addEventListener(l, ctx.evtListeners[l]);
    }
};

var cleanup = function(args, ctx) {
    for (var l in ctx.evtListeners) {
        ctx.domElement.removeEventListener(l, ctx.evtListeners[l]);
    }
};

var startDrag = function(ctx, x) {
    ctx.dragging = true;
    ctx.currentX = x;
};

var stopDrag = function(ctx) {
    ctx.dragging = false;
};

var drag = function(ctx, x) {
    if (ctx.dragging) {
        ctx.lastX = ctx.currentX;
        ctx.currentX = x;
        ctx.velocity += ctx.currentX - ctx.lastX;
    }
};

var update = function(args, ctx) {
    ctx.entity.addRotation(0, args.sensitivity * ctx.velocity * ctx.world.tpf, 0);
    ctx.velocity *= (1-args.damping);
};

var parameters = [{
    key: 'sensitivity',
    name: 'Sensitivity',
    type: 'float',
    control: 'slider',
    min: 0.01,
    max: 1.00,
    default: 0.05
},{
    key: 'damping',
    name: 'Damping',
    type: 'float',
    control: 'slider',
    min: 0,
    max: 1,
    default: 0.05
}];

Play your scene and spin the Box with your mouse.

Now publish and share your scene!

We recommend you check out the Amazon Sumerian Events tutorial to see how to achieve more advanced scene interaction. Additionally, here are some ideas for enhancing the scene you just created:

  • Add functionality for the click or double-click events.
  • Add a Skybox and try rotating the camera around the object, maybe at a different speed.
  • Also implement functionality for the Y coordinate. How about seamlessly changing the color of the entity?
  • Add an HTML component which shows the current rotation speed.
  • Create some effect when the speed reaches a certain limit. Try setting the object on fire!

Try out the following tutorials:

Back to Tutorials

© 2019 Amazon Web Services, Inc or its affiliates. All rights reserved.