Implementing a custom drag event function in JavaScript and THREE.js
By: Daniel Ellis
Tags: JavaScript, Webgl, Drag, D3js, Threejs

THREEjs is a cross-over JavaScript library that allows us to unleash the potential of GPU driven graphics within the web browser. Although it provides both a orbitalControls and dragControls functions for object or scene manipulation, but what if we want a more personalised response on drag events? — this is what we shall cover here.

The code discussed within the article can apply to any HTML element, although the emphasis, in this case, is placed upon THREEjs. The only difference falls upon what goes into the dragAction function at the end.

— If you are new to the THREE.js library, have a browse through the following materials: here

— If you wish to apply the same principles to Data-Driven Documents (d3.js), have a look here

The Event Listener

To determine what is happening within our program we start by observing a number of mouse events. As the user ‘drags’, we need to know when they click, move and then release the object — in JavaScript, this is done through the use of event listeners.

Each Event ( mousedown , mousemove and mouseup ) is attached to a DOM element within the webpage. In our case, it is the canvas upon which the THREE libraries is plotting on. We can get this by using

let canvas = renderer.domElement

Default Variables

We begin by defining a set of default variables which our program will update. Here we have a logical ‘is the mouse down?’ mouseDown variable, and the cursor position variables.

var mouseDown = false,

mouseX = 0,

mouseY = 0;

Mouse Down

The first check we want to make is to see if our user has pressed the mouse button — as without this we do not have a ‘drag’ event. We do this by listening for a mousedown event on our canvas element.

canvas.addEventListener('mousedown', function (evt) {

evt.preventDefault();

mouseDown = true;

mouseX = evt.clientX;

mouseY = evt.clientY;

}, false);

When this happens we update our mouseDown logical variable and record the current cursor position.

Mouse Move

Now we have a pressed button, we want to track how much the user drags the mouse across the screen. We can do this as so:

canvas.addEventListener('mousemove', function (evt) { if (!mouseDown) {return} // is the button pressed? evt.preventDefault();

var deltaX = evt.clientX - mouseX,

deltaY = evt.clientY - mouseY;

mouseX = evt.clientX;

mouseY = evt.clientY; dragAction(deltaX, deltaY,object); }, false);

This function is activated each time the user moves their mouse. If the mouse button has not been pressed, however, it does not do anything.

If the button is pressed, our user is ‘dragging’ across the screen. We therefore record the distance moved, and pass it on to a custom dragAction function, along with the object we wish to manipulate.

Mouse Up

Finally, we need to know when the user stops pressing the mouse button, and reset the mouseDown variable. This is done with the mouseup event listener:

canvas.addEventListener('mouseup', function (evt) {

evt.preventDefault();

mouseDown = false;

}, false);

The custom response function

So far we have looked at recording when the user applies a ‘drag’ event and how far they have moved the cursor throughout this. Now we create a function to pass that information to the part of our scene.

For the purpose of this example, we will be rotating a mesh object — or in my case a THREE.Group containing 5 objects (see here for groups). We do this by changing the x and y rotation as such:

function dragAction(deltaX, deltaY,object) {

object.rotation.y += deltaX / 100;

object.rotation.x += deltaY / 100;

}

Here deltaX and deltaY represent the relative change of the mouse cursor throughout the drag event. The actions however can be anything the user desires — for instance, if we wanted to translate the position, we could use object.position.x += X instead.

Putting it all together

As I plan to reuse this code, It is not within my interest to copy-paste each time I wish to apply it. Instead, I can package it into a module, place that within a shared folder and import it each time I desire to use it. The module contents are given below:

Importing the module

Now we have a module, we can import it within our script as :

import {dragControls,dragAction} from './dragTHREE.js';

and use it as

dragControls(renderer.domElement,dragAction,graphs)

Where renderer.domElement is my canvas, dragAction is a function which takes the mouse positions and an object as an argument, and graps is a group object containing all the individual components of each graph in the title image.

Using a personal function with the module

Since the module is available to edit, we can either change the dragAction function directly, or just pass a different one to dragControls :