Doing what we want, how we want it, on user ‘drag’ interaction in THREEjs (or d3 or anything really).
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 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.
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:
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:
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 :
If you were using d3 you could change the shape of a rectangle to the horizontal and vertical distance scrolled in the same way.
// create our rectangle at (0,0) with a width of 200px ... svg.append("rect") .attr("x", 0) .attr("y", 0) .attr("width", 200) .attr("height", 100) .attr("id","myrectangle")function squish(deltaX, deltaY,object) { var rect = d3.select('#myrectangle') // get current measurements var w = rect.attr('width') var h = rect.attr('height')// update width and height values rect.attr('width', w+deltaX) .attr('height',h+deltaY) } // dont forget to make sure you pass the DOM element and not a d3 wrapped object. To do this use svg.node()dragControls(svg.node(),squish,'none')
Conclusion
And there we have it, the basics on drag object manipulation in JavaScript, used mainly to manipulate WebGL objects in THREEjs, but also work for all elements. This means we can also use them on SVG’s or to manipulate a canvas in d3.js. However, if you are using d3, I would strongly recommend looking at the d3.drag and selection libraries at