Mouse Events

The most commonly used device to interact or view a website is the mouse. In this chapter, we will delve deeper into the two sub categories of mouse events, mouse click and mouse move.

Mouse Clicks

We had established that there are three events that fire during a mouse click. "mousedown" is fired the moment a mouse button is pressed. "mouseup" is fired after the button is released. Immediately after the "mouseup" event, "click" event is fired. The events will originate from the DOM nodes that are below the mouse pointer as they occur.

For "mouseup" and "mousedown", the target DOM nodes are easy to determine. For the "click" event, the target will be the most specific element that contains the DOM nodes firing the "mousedown" and "mouseup" events. So if you have a div element containing two paragraphs. And you press down on one paragraph, drag your pointer to the other paragraph and releases the button. The "click" event will be fired from the div element.

If two click events happen consecutively, a "dblclick" double click event is fired, right after the second click event.

A simple Repl example is built to demonstrate these events.

<div id="container1">
  <div id="box1">Box 1</div>
  <div id="box2">Box 2</div>
</div>

<div id="log"></div>

A div element containing two div elements are created. An event listener for "click", "mousedown", and "mouseup" are added to the window object. The event target is matched against the IDs of the three div elements. When a specific event fires, a short message is added to the #log div.

var log = document.querySelector("#log");
var prependChild = function (newNode) {
  this.insertBefore(newNode, this.firstChild);
};

var appendMessageToLog = function (message) {
  var newP = document.createElement('p');
  var newTextNode = document.createTextNode(message);
  newP.appendChild(newTextNode);
  prependChild.call(log, newP);
};

var container = document.querySelector("#container");
window.addEventListener("click", function(event) {
  if (event.target.matches("#container1") || event.target.matches("#box1") || event.target.matches("#box2")) {
    appendMessageToLog(event.target.id + ": click");
  }
});

window.addEventListener("mousedown", function(event) {
  if (event.target.matches("#container1") || event.target.matches("#box1") || event.target.matches("#box2")) {
    appendMessageToLog(event.target.id + ": mousedown");
  }
});

window.addEventListener("mouseup", function(event) {
  if (event.target.matches("#container1") || event.target.matches("#box1") || event.target.matches("#box2")) {
    appendMessageToLog(event.target.id + ": mouseup");
  }
});

window.addEventListener("dblclick", function(event) {
  if (event.target.matches("#container1") || event.target.matches("#box1") || event.target.matches("#box2")) {
    appendMessageToLog(event.target.id + ": dblclick");
  }
});

You can see the timing of the events firing and the target they are fired from by varying your mouse click. You can try pressing down on box1, move across to box2, then release. You will notice the "mousedown" event is fired by "box1". "mouseup" is fired by "box2" and "click" is fired by "container1".

Finally, if you doubled clicked on any of the boxes, the "dblclick" event will be fired in the end.

Try it on Repl

Mouse Movements

I won't be exaggerating if I told you all events on the browser can be tracked. This includes the movement of your mouse pointer. When the mouse pointer moves, an event called "mousemove" is fired. We can use this to create some fun interactions. To keep things simple, we will create a small round blob that follows the movement of the mouse on the screen.

<div id="blob"></div>
<style>
#blob {
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: black;
}
</style>

The event object emitted has properties called pageX and pageY which will give you the position of the pointer in relation to the top left corner of the entire page. The coordinates are measured in pixels (px). We can use these coordinates to reposition our blob.

var blob = document.querySelector("#blob");

window.addEventListener("mousemove", function (event) {
  blob.style.left = event.pageX + "px";
  blob.style.top = event.pageY + "px";
});

Try it on Repl

Apart from "mousemove", there are other events such as "mouseenter", "mouseover", "mouseleave". We could use "mouseover" to trigger some programs when the mouse pointer is hovering above it.

"mouseenter" and "mouseleave" events do not bubble. "mouseenter" will trigger only when you are moving the mouse pointer from an external space into an element's space. "mouseleave" will only trigger when the mouse pointer leaves the element and all of its children.

"mouseover", however, bubbles. When you hover above a child element, the "mouseover" event will bubble through its parents all the way up to the root element.

Lets take a look at how these events fire by creating 3 nested elements.

<div id="box1">
  <span>Box 1</span>
  <div id="box2">
    <span>Box 2</span>
    <div id="box3">
      <span>Box 3</span>
    </div>
  </div>
</div>

<div id="log"></div>

We will add the event handlers directly to the elements instead of the window because "mouseenter" and "mouseleave" do not bubble. For the "mouseover" event, we are also attaching handlers to each of the boxes so we can demonstrate that the event bubbles.

var log = document.querySelector("#log");
var prependChild = function (newNode) {
  this.insertBefore(newNode, this.firstChild);
}

var appendMessageToLog = function (message) {
  var newP = document.createElement('p');
  var newTextNode = document.createTextNode(message);
  newP.appendChild(newTextNode);
  prependChild.call(log, newP);
}

var box1 = document.querySelector("#box1");
var box2 = document.querySelector("#box2");
var box3 = document.querySelector("#box3");

box1.addEventListener("mouseenter", function(event) {
  appendMessageToLog(event.target.id + ": mouseenter");
});
box2.addEventListener("mouseenter", function(event) {
  appendMessageToLog(event.target.id + ": mouseenter");
});
box3.addEventListener("mouseenter", function(event) {
  appendMessageToLog(event.target.id + ": mouseenter");
});

box1.addEventListener("mouseleave", function(event) {
  appendMessageToLog(event.target.id + ": mouseleave");
});
box2.addEventListener("mouseleave", function(event) {
  appendMessageToLog(event.target.id + ": mouseleave");
});
box3.addEventListener("mouseleave", function(event) {
  appendMessageToLog(event.target.id + ": mouseleave");
});

box1.addEventListener("mouseover", function(event) {
  appendMessageToLog("box1: mouseover");
});
box2.addEventListener("mouseover", function(event) {
  appendMessageToLog("box2: mouseover");
});
box3.addEventListener("mouseover", function(event) {
  appendMessageToLog("box3: mouseover");
});

By combing the "mouseenter" and "mouseleave" event, you can mimic what the CSS pseudo class :hover does. But if all you want to do is to just change some styles to an element when it's being hovered, I would highly recommend you to use the CSS :hover pseudo selector instead. It is a lot less work and much less likely to introduce bugs.

Draggable Bar

To put our knowledge into practice, we are going to create a draggable bar element using a bit of JavaScript. First, we will start with the bar. A simple div element will work.

<p>Click and drag the bar with your mouse.</p>
<div id="bar"></div>
#bar {
  background: #42A5F5;
  height: 30px;
  width: 50px;
}

Before we write the JavaScript, lets first establish the target behavior we want.

  1. The bar will increase or decrease its length based on the motion of the mouse pointer.
  2. When the mouse pointer moves from the base of the bar (left) to the tip (right), the bar increases in length, and vice versa.
  3. The mechanism will only activate after the user is pressed down on the primary button of the mouse within the area of the bar element.
  4. The bar should have a minimum length.

Ok, lets attempt point 1, 2 and 4 first. We are going to listen for the "mousemove" event. And when the mouse is moving on the page, we will calculate how much it has moved in the "x" direction relative to the last "mousemove" event. This means we will need to remember the previous x position, so we can calculate how much we should adjust the width of the bar based on the current x position of the mouse pointer.

var prevPageX;
var bar = document.querySelector('#bar');

window.addEventListener('mousemove', function (event) {
  if (prevPageX) {
    var distanceMoved = event.pageX - prevPageX;
    var newBarWidth = Math.max(20, bar.offsetWidth + distanceMoved);
    bar.style.width = newBarWidth + 'px';
  }
  prevPageX = event.pageX;
});

Explain: The HTMLElement.offsetWidth read-only property returns the layout width of an element. Typically, an element's offsetWidth is a measurement which includes the element borders, the element horizontal padding, the element vertical scrollbar (if present, if rendered) and the element CSS width. - MDN

With just this code, the bar will move whenever you are moving your mouse on the page, much like the blob we created previously which follows the mouse pointer.

Now, we will implement point 3, only activating the move mechanism after the user presses down the primary mouse button within the bar. For this, we will listen to the "mousedown" event on the bar.

Then we will add the event handler for "mousemove" only when the user is pressing down on the primary button.

To stop resizing the bar, we will remove the event handler for "mousemove" if the user is no longer pressing down on the primary button.

We will move the callback function into a variable called "resizeBar" and add an if statement that removes the "mousemove" event handler from bar.

var prevPageX;
var bar = document.querySelector('#bar');

var resizeBar = function (event) {
  if (event.buttons !== 1) {
    window.removeEventListener('mousemove', resizeBar);
  } else {
    var distanceMoved = event.pageX - prevPageX;
    var newBarWidth = Math.max(20, bar.offsetWidth + distanceMoved);
    bar.style.width = newBarWidth + 'px';
    prevPageX = event.pageX;
  }
};

bar.addEventListener('mousedown', function (event) {
  if (event.buttons === 1) {
    prevPageX = event.pageX;
    window.addEventListener('mousemove', resizeBar);
  }
});

Notice we used the event.buttons property instead of the event.button property in the previous chapter. event.buttons will accurately return the current pressed state of the mouse buttons where as event.button will only indicate the button that triggered the event for "mousedown", "mouseup", "click", and "dblclick".

Explain: The MouseEvent.buttons property indicates the state of buttons pressed during any kind of mouse event, while the MouseEvent.button property only guarantees the correct value for mouse events caused by pressing or releasing one or multiple buttons. - MDN

The number value returned by MouseEvent.buttons is also different from MouseEvent.button.

  • 0 : No button or un-initialized
  • 1 : Primary button (usually left)
  • 2 : Secondary button (usually right)
  • 4 : Auxilary button (usually middle or mouse wheel button)
  • 8 : 4th button (typically the "Browser Back" button)
  • 16 : 5th button (typically the "Browser Forward" button)

If multiple buttons are pressed at the same time, the sum of the individual values are returned, i.e. 3 for Primary + Secondary, 6 for Secondary + Auxilary.

Try it on Repl

results matching ""

    No results matching ""