How to Write Your Own Custom Mouse UI Class In Vanilla JavaScript
You can Follow me on Twitter to get tutorials, JavaScript tips, etc.
By making my own custom Mouse class I was able to build on top of it to implement all of the following completely custom UI controls — including the re-sizable and draggable toolbar itself.
So let’s dive in and see how we can do this.
Information overload is a real challenge for new coders.
If you are learning to code but doubting whether you are learning “the right subjects” (you know, the ones that buff up your experience and bring you one step closer to becoming employable) look no farther than the mouse class.
If you haven’t programmed your very own mouse class yet — you should.
Here are the top reasons to write your own mouse class:
- It’s Primal — Everything we do in UI space is based on mouse movement.
- It’s Re-usable — Write once, deploy in any project (even at work).
- It’s Practical — I can’t count how many things you can build based on it by having the mouse class in your arsenal of re-usable modules.
- It’s Reputable — Publish it to GitHub. Next time someone asks: “Why not just download an NPM module for that?” You can say: “Thank you, thank you very much, but I am a real programmer and tons of people are already downloading the one I wrote. It’s how I got my current employer’s attention.”
- It’s The Cure For Impostor Syndrome — Your boss asks you to create a custom slider or scroll-bar control. (It’s the job you wouldn’t take anyway because you thought you didn’t qualify for it.) So, what do you want to do? Download an existing NPM module, write one line of object instantiation code and tell your boss you did it? Or learn how to actually write code and be a practical programmer?
What are we doing?
Mouse class sounds fine. But what function should it have? How should we decide which properties and methods our class should have?
First we need to identify a set of problems we want our class to solve.
1. Track Movement
We need to know mouse x and y position globally at any given time.
2. Dragging State
Dragging things around with the mouse is what it’s all about. This is the core function of the class we’re going to write.
We’ll simply define it as this.isDragging = false;
in our class constructor.
A mouse can be openly floating across the screen space or it can be dragging an object (or a slider knob, for example) around if it’s pressed down on it.
The drag state of the mouse should be global. It should be tracked independently of whether it is clicking on any particular object or not. At any time in our application.
This way, when writing our own custom UI controls later, we should never worry about calculating mouse or dealing with custom mouse events ever again.
3. Mouse Travel Distance
We must know the distance the mouse has traveled since the last time the mouse was clicked. At this point we will memorize its click location. We’ll use a simple object literal for that: this.memory = {x: 60, y: 50}:
We’ll also track and store mouse distance in mouse.difference.x and .y
The distance is calculated while the mouse is moving and the mouse button is still pressed. That’s the only span of time during which we need to know it. Once the button is released again all coordinates are reset to 0.
Again, the distance is calculated globally, independent of any UI controls. When we code our own UI controls, we will have drag distance in pixels available to us via this mouse class, not the UI control class.
3 Main Mouse Events
If we can only capture the following events we can write the code around it:
- Mouse Button Pressed Down (mousedown)
- Mouse Button Unpressed (mouseup)
- Detect Mouse Movement (mousemove)
We will use addEventListener
to listen to each event separately:
/* replace event-name with mousedown, mouseup or mousemove */
document.body.addEventListener("event-name", (e) => { ... }
We must use arrow function (e) = > { … } in our class as callback for events.
If you use standard function() { … } syntax then the this object in it will not refer to our class object instance. Something we need to avoid because we want to write to our class properties (this.current.x or this.memory.x for example) and not some other execution context.
Knowing all this — it’s easy to implement the Mouse class.
It is just 3 mouse event listeners executed in the Mouse class constructor.
Here’s What We Got So Far
Here’s the complete source code for our newly created Mouse class:
export class Mouse { constructor() { this.current = {x: 0, y: 0}; // Current mouse position on
// the screen, at any time
// regardless of state this.memory = {x: 0, y: 0}; // Memorized mouse position
// (for measuring dragging
// distance) this.difference = {x: 0, y: 0}; // Difference this.inverse = {x: 0, y: 0}; // Handle negative distance this.dragging = false; // Not dragging by default // Mouse Button Was Clicked!
document.body.addEventListener("mousedown", (e) => { // We are not currently dragging
if (this.dragging == false) { // Start tracking dragging coordinates
this.dragging = true; // Memorize mouse click location
this.memory.x = this.current.x;
this.memory.y = this.current.y; // Reset inverse coordinates
this.inverse.x = this.memory.x;
this.inverse.y = this.memory.y;
}
}); // Mouse Button Was Released!
document.body.addEventListener("mouseup", (e) => { // Mouse button has been released, disable drag state
this.dragging = false; // Reset everything to 0
this.current.x = 0;
this.current.y = 0;
this.memory.x = 0;
this.memory.y = 0;
this.difference.x = 0;
this.difference.y = 0;
this.inverse.x = 0;
this.inverse.y = 0;
}); // Capture Mouse Move Event
document.body.addEventListener("mousemove", (e) => { // Get the mouse coordinates
this.current.x = e.pageX;
this.current.y = e.pageY; // If mouse is currently being dragged
if (this.dragging) { this.difference.x = this.current.x - this.memory.x;
this.difference.y = this.current.y - this.memory.y; if (this.current.x < this.memory.x)
this.inverse.x = this.current.x; if (this.current.y < this.memory.y)
this.inverse.y = this.current.y;
}
}); } // end constructor
}; // end class
Save this code in mouse.js.
Note: mouse.inverse.x and mouse.inverse.y refer to the upper left corner of the selection box in dragging state, regardless which way the user drags the mouse. Moving up and left will create a box with “negative” values. Moving down and right will create a box with “positive” (normal) values.
When you click the mouse button and drag upward and to the left, then the top left corner of the selection will produce negative values. But no matter what direction you drag, mouse.inverse.x / y ensures to always refer to the upper left corner’s x and y position.
Learn It, Write It, Save It, Reuse It.
Every time you need automatic mouse movement and drag distance info in your app, simply add it to your project as follows.
Place both index.html (below) and mouse.js into the same folder.
<script type = "module">import { Mouse } from "./mouse.js";let mouse = null;
window.onload = () => { // Wait until all media is loaded
mouse = new Mouse(); // Instantiate mouse object
};// ===== Automatically available throughout the app =====mouse.x; // Current x position
mouse.y; // Current y position
mouse.memory.x; // Last clicked location on x
mouse.memory.y; // Last clicked location on y
mouse.isDragging; // Was mouse clicked down and dragging now?
mouse.distance.x; // Distance of the drag on x
mouse.distance.y; // Distance of the drag on y
mouse.inverse.x; // Always upper left corner of drag box
mouse.inverse.y; // Always upper left corner of drag box</script>
You can reuse this class without having to deal with mouse coordinates and drag distance ever again anywhere in your future applications — from now on they are openly exposed globally in your app.
You can start building custom UI controls by including this class in your app.
Thanks for reading!
Now go build your own UIs!
Check out my CSS Book on Amazon — a pictorial approach to CSS.