Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

JavaScript Mouse Events

Chapter 62 33 mins

Learning outcomes:

  1. The MouseEvent interface
  2. The mouse click cycle — mousedown, mouseup, click
  3. Right-click event — contextmenu
  4. The mouseenter and mouseleave events
  5. The mouseover and mouseout events
  6. The mousemove event

Introduction

Now that we've covered the basics of events in JavaScript we can finally move on to consider some real events, besides the fairly monotonous click. In this chapter we'll take a look over the mouse category of events i.e. all the events dispatched by a mouse.

Specifically, we'll examine the events mousedown, mouseup, click, contextmenu followed by mouseover, mouseout, mouseenter and mouseleave, before ending with mousemove.

So let's start the exploration.

The MouseEvent interface

In the chapter JavaScript Events — Event Objects, we learnt that all events (i.e. event objects) in JavaScript inherit from the Event interface. Talking specifically about mouse events, they also abide by this rule but not directly.

Instead, mouse events inherit from the MouseEvent interface which then inherits from UIEvent which finally inherits from Event.

MouseEvent adds many many useful properties on mouse events for us to work with.

For example, using the clientX and clientY properties, we could determine the exact coordinates of the mouse pointer relative to the browser window. This could then be used to implement other useful features such as showing widgets right at the position of the mouse pointer. We'll see such examples later on in this chapter.

The table below details some of the most useful properties of MouseEvent interface.

PropertiesPurpose
screenX / screenYSpecify the x and y coordinates of the pointer relative to the screen, respectively.
clientX / clientYSpecify the x and y coordinates of the pointer relative to the browser window, respectively.
pageX / pageYSpecify the x and y coordinates of the pointer relative to the webpage, respectively.
x / yAlias of clientX and clientY, respectively.
altKeyReturn a Boolean specifying whether or not the Alt key is pressed.
metaKeyReturn a Boolean specifying whether or not the meta key is pressed. On Windows OS, the meta key is the (Windows) key, while on Mac OS, it is the (Command) key.
ctrlKeyReturn a Boolean specifying whether or not the Ctrl key is pressed.
shiftKeyReturn a Boolean specifying whether or not the Shift key is pressed.
buttonReturns a number representing the last button currently pressed on the mouse.
buttonsReturns a number representing all of the buttons currently pressed on the mouse.
relatedTargetReturns the target related to the current event.
The MouseEvent interface defines two methods as well — getModifierState() and initMouseEvent() — but they're not that important as compared to the interface's properties. So for brevity, we have omitted them.

Throughout this chapter, we'll work with most, if not all, of these properties and thus get a better sense of each of them.

The click lifecycle

So far in this course, we've learnt that when we press the mouse button (or more precisely, press the left button of the mouse), the click event occurs. Although this is correct, it's just one part of the whole story.

During this mouse-click action, a couple of other events fire as well, and they are equally important to be understood as click.

The sequence of events that fires from the moment we press down the mouse button to the moment we leave it, a maximum of three events fire:

  1. mousedown — indicates that the mouse button has gone down.
  2. mouseup — indicates that the mouse button has gone up.
  3. clickour very own click!

This is the lifecycle of the click event. When click fires, it's known for sure that the mousedown and mouseup events have occured.

However, sometimes, mousedown and mouseup might fire, but click won't and why that happens exactly is detailed later on in this chapter, in the click event section.

For now, let's take a deeper look into these two new events, i.e. mousedown and mouseup, before turning our attention back to the familiar click.

A touchscreen won't be able to distinguish between these three events, so in order to better visualize them, consider using a mouse or any other pointing device such as a trackpad.

The mousedown event

The mousedown event fires on an element if the mouse's left button is pressed, i.e. goes down, while the pointer is over the element.

It's actually way easier seen in an example than explained. So likewise let's consider an example.

Consider the following code where we set up a large <div> element so that we could easily test given mouse events on it:

<div id="mouse-area"></div>
#mouse-area {
   height: 150px;
   border: 1px dashed grey
}

Now, in the following JavaScript, we select this <di> element and then set up a handler for mousedown, making an alert inside it:

var element = document.getElementById('mouse-area');

element.onmousedown = function(e) {
   alert('mousedown fired.');
}

Below we have a live example.

Go on and try to press the left button of your mouse over this <div>.

Make sure you don't leave your finger off the button, otherwise subsequent events in the line, i.e. mouseup and then click, will fire and so it'll be difficult for you to judge the difference between all these three events.

The mousedown event also fires for the right button of the mouse. However, it's less common to use the right button in this way — it's mostly used to open up context menus in applications. Remember right-click menus?

This is great.

But let's now try a slightly more complex example.

We'll extend the same code shown above, but instead of merely alerting a message upon mousedown, we'll display a small animated circle exactly at the position where the mousedown event occured.

And for this, we'll utilize the clientX and clientY properties of the fired mouse event.

First, let's define the styles of the circle, represented as .circle, in the CSS so that we could easily use it later on:

.circle {
   position: absolute;
   height: 20px;
   width: 20px;
   background-color: lightblue;
   border-radius: 10px;
   transform: translate(-50%, -50%);
   transform-origin: left top;
   animation: zoom 10s 1 both;
}

@keyframes zoom {
   to {
      transform: scale(3) translate(-50%, -50%)
   }
}
To learn more about CSS animations, refer to CSS Animations — Basics. To learn more about transformations, refer to CSS Transformations — Introduction.

Since the circle will be positioned absolutely, we'll have to add position: relative to the containing #mouse-area element and even the overflow: hidden declaration in order to hide overflowing circles:

#mouse-area {
   position: relative;
   overflow: hidden;
   height: 150px;
   border: 1px dashed grey;
}

And now, it's time for the real magic!

Here's the JavaScript code:

function createCircle(left, top) {
   var circleElement = document.createElement('div');
   circleElement.className = 'circle';

   circleElement.style.left = `${left}px`;
   circleElement.style.top = `${top}px`;

   mouseAreaElement.appendChild(circleElement);
}


var mouseAreaElement = document.getElementById('mouse-area');

mouseAreaElement.onmousedown = function(e) {
   createCircle(e.clientX - this.offsetLeft, e.clientY - this.offsetTop);
}

Live Example

Here's what's happening:

  1. The moment a click is made inside #mouse-area, the position of the mouse pointer is determined, via e.clientX and e.clientY.
  2. Then using this position relative to the client window, the corresponding values for the left and top CSS properties of .circle are determined. This obviously requires us to substract the left and top offsets of #mouse-area (relative to the document) from the x and y coordinates of the pointer (also relative to the document window).
  3. Once the correct position for a circle is determined, it's created via the call to createCircle().
  4. createCircle() creates a new <div> element node, sets the respective attributes on it, and then finally appends the node inside the #mouse-area element.

Simple?

To learn more about offsetLeft and offsetTop, refer to CSSOM Elements — Offsets.

The mouseup event

The mouseup event fires after mousedown, as soon as the mouse button gets released.

It represents the action of the mouse's button 'going up', hence the name.

Note that for mouseup to fire on an element, it isn't necessary for mousedown to have fired on the same element before. It's just necessary for the mouse button to go up while the pointer is over the element.

Let's take an example, and the same one that we saw before:

<div id="mouse-area"></div>
var mouseAreaElement = document.getElementById('mouse-area');

mouseAreaElement.onmouseup = function(e) {
   alert('mouseup fired.');
}

Below we have a live snippet:

Go on, start from anywhere outside this region, and then move into it, while leaving off the mouse button. Just try to originate mousedown outside this region and then bring the pointer into the region before leaving off the mouse button — you'll see the magic!

Great.

And now, let's improvise on our previous circle-creating program by incorporating a mouseup event in there. The idea is that when mouseup fires, we remove the last circle shown inside #mouse-area.

Here's the new JavaScript code:

function createCircle(left, top) {
   var circleElement = document.createElement('div');
   circleElement.className = 'circle';

   circleElement.style.left = `${left}px`;
   circleElement.style.top = `${top}px`;

   previousCircleElement = circleElement;
   mouseAreaElement.appendChild(circleElement);
}


var mouseAreaElement = document.getElementById('mouse-area');
var previousCircleElement = null; mouseAreaElement.onmousedown = function(e) { createCircle(e.clientX - this.offsetLeft, e.clientY - this.offsetTop); }
mouseAreaElement.onmouseup = function(e) {
if (previousCircleElement) {
previousCircleElement.parentNode.removeChild(previousCircleElement);
}
}

Live Example

Notice the additions here compared to the previous example:

  • The variable previousCircleElement is meant to hold the previous .circle element, that was created by virtue of the mousedown event.
  • The mouseup event's handler serves to remove the element stored inside previousCircleElement, if there happens to be one (which is checked with a simple if condition).

The click event

The click event is perhaps one of the most rudimentary events in JavaScript, yet it might not be fully understood by some developers. As we already know, the event fires when we click on something.

But what exactly does it mean to 'click' on something? What exactly is the 'click' action over here?

That's where the technicality lies.

Essentially, an element gets 'clicked' when the preceding mousedown and mouseup events both occur on that same element. If either of the events occurs outside the element, then the whole action doesn't constitute a click in the end.

Let's consider a quick example using the same #mouse-area element:

<div id="mouse-area"></div>
var mouseAreaElement = document.getElementById('mouse-area');

mouseAreaElement.onclick = function(e) {
   alert('click fired.');
}

Below we have a live snippet:

Try replicating the action that you did above to test the mouseup event on this #mouse-area element. This time, you'll notice that click won't fire and that's because the preceding mousedown event doesn't take place inside the element.

In other words, we could say that the click event is merely a more sophisticated version of mouseup.

As in each of the two events above, we'll now consider a slightly more complex example for click. However, it won't be related to our circle-creating program. Rather, it'll be related to emulating the click activation behavior of links.

You would obviously know what happens when we click a link — the browser loads the corresponding href. However, do you know what happens when we do so but with the Ctrl key pressed?

Well, as before, the browser loads the underlying href, however not in the same page, but instead in a new page.

Let's try tapping into both of these behaviors.

Here's a simple link on which we'll experiment our click handlers:

<a href="https://www.codeguage.com/">This is a link</a>

The idea is that if the link is clicked without the Ctrl key pressed, we'll alert the link's href only. Otherwise, if the Ctrl key is pressed, we'll again alert the href of the link followed by the text 'New window' on a new line.

To check whether the Ctrl is pressed right at the moment the click event fires, we'll use the ctrlKey property of the event. As stated before, it returns true if Ctrl is pressed, or else false.

Here's the code to accomplish this:

var anchorElement = document.querySelector('a');

anchorElement.onclick = function(e) {
   e.preventDefault();
   if (!e.ctrlKey) {
      alert(this.href);
   }
   else {
      alert(this.href + '\nNew window');
   }
}

Live Example

Don't forget the call to e.preventDefault() in the handler above because without it, a click on the link would end up triggering its activation behavior and then, ultimately, loading the browser window to the new href.

To learn more about preventDefault() and activation behaviors, refer to JavaScript Events — Event Objects — preventDefault().
While working with these live instances and moving around with the mouse key held down, you might encounter the blocked pointer. This can arise when you originate your mousedown event on a piece of text and then move from that point - the browser realises this as a drag event and thus cancels the operation. To prevent this from happening use the preventDefault() method.

Right click?

You might be wondering that for the right mouse button, JavaScript would have an event spelling something like onrightclick. If you did think so, then you predicted the wrong name, but fortunately a feature that JavaScript does indeed provide.

The event is called contextmenu. The reason it's called this is because when we right-click the mouse, typically, a menu appears and this menu is referred to as the 'context menu'.

Let's consider an example of contextmenu in action.

Below is the same #mouse-area configuration as before:

<div id="mouse-area"></div>

And following we handle the contextmenu event on this element, making an alert when it happens:

var mouseAreaElement = document.getElementById('mouse-area');

mouseAreaElement.oncontextmenu = function() {
   alert('contextmenu fired.');
}

Go ahead and try right-clicking below — you'll surely get the alert!

Quite basic, wasn't it?

Talking about the practical significance of contextmenu, if you want to design a custom context menu for your application and get it to be displayed instead of the conventional and typical menu, you could use this event to your advantage.

Just call the event's preventDefault() method to stop the default menu from appearing, and then carry on with whatever code you want to get executed thereafter. To get an idea check out the exercise that follows this chapter.

Mouse enters and leaves

Apart from listening to events dispatched, more or less, because of pressing the buttons on a mouse, another common concern of applications is to handle events fired as we just move the mouse pointer across the screen.

In this regard, we have the provision of a handful of events: mouseenter, mouseleave, mouseover, mouseout, and mousemove. In this section, we'll begin with exploring the mouseenter and mouseleave events.

The mouseenter event fires on an element the moment the mouse pointer is taken inside it. It's literally when the mouse pointer 'enters' the element, hence the name.

Similarly, the mouseleave event fires when the pointer is taken outside the element. Once again, it's literally when the mouse pointer 'leaves' the element, hence the name.

Let's consider an example.

Say we want to change the background color of the following paragraph to yellow the moment you bring the mouse pointer inside it:

<p>A paragraph</p>
p {
   padding: 15px;
   background-color: #ddd;
}

A paragraph

Well, the task is quite simple. Here's the script to accomplish it:

var paraElement = document.querySelector('p');

paraElement.onmouseenter = function() {
   this.style.backgroundColor = 'yellow';
}

In the live example below, try bringing the mouse pointer inside the paragraph shown below. You'll see how it'll change its background color:

A paragraph

Amazing!

Notice one thing in the example above. When we bring the pointer into the element, its background color does indeed change, however when we bring the pointer out of it, its background color doesn't reset to its initial value.

We shall now solve this problem by handling mouseleave.

It's really simple — change the background color back to #ddd inside the onmouseleave handler:

var paraElement = document.querySelector('p');

paraElement.onmouseenter = function() {
   this.style.backgroundColor = "yellow";
}

// Reset to initial background.
paraElement.onmouseleave = function() {
   this.style.backgroundColor = '#ddd';
}

A paragraph

As you might've realized, this whole task could've been accomplished more neatly using CSS, in particular, using the :hover pseudo class.

However, remember that this doesn't apply to all tasks! Some do require handling mouseenter and mouseleave.

Mouse over and out

Besides mouseenter and mouseleave, there exist two similar, yet confusing, mouse events. They are mouseover and mouseout.

For a given element, the mouseover event fires on it when the pointer is brought inside it or inside any of its descendants. The event refires when the pointer is taken from the element to one of its descendants or vice-versa.

On the same lines, the mouseout event fires on an element when the pointer is taken outside the element or outside any of its descendants. The event refires when the pointer is taken from the element to one of its descendant or vice-versa. It also, obviously, fires when the pointer is taken outside the element.

The main difference between mouseenter/mouseleave and mouseover/mouseout lies in the word 'refires.'.

For the former, as soon as we take the pointer inside an element (that handles mouseenter), the mouseenter event will fire and then while we move the pointer only inside the element, no further mouseenter or mouseleave events will get dispatched. Simple.

However, the same doesn't apply for mouseover and mouseout — they work differently. They will fire even after we enter the element for the very first time and then just move the pointer only within the bounds of that element.

The example below will help clarify what this means.

Suppose we have the following HTML, with a <div> element holding a <span>:

<div>A <span>span</span> in a div</div>

Further suppose that the following CSS is in action:

div {
   background-color: #ddd;
   padding: 10px;
}

span {
   display: inline-block;
   padding: 15px;
   background-color: orange;
   color: white
}

.pink {
   background-color: pink
}

Here's how the output looks:

A span in a div

Now with all this in place, let's first investigate the mouseenter and mouseleave events.

What we'll be doing is to toggle the CSS class 'pink' on the <div> element each time the pointer is brought into it and out of it. And this will be done using classList.toggle().

If the class is set on the <div>, it'll be removed, otherwise it'll be added.

To learn more about classList.toggle(), refer to HTML DOM — Attributes — classList.

Here's the JavaScript code:

var divElement = document.querySelector('div');

divElement.onmouseenter = function() {
   this.classList.toggle('pink');
}

Try experimenting around with the live result shown below:

A span in a div

When you move your mouse pointer into the <div> element, its background changes to pink. Now when you take the pointer out and then again into the element, its background changes back to #ddd.

Most importantly, after you enter the <div>, and then hover over <span>, nothing happens. This is because the mouseenter event does NOT refire as we move just inside the <div>.

But, as we just learnt, the mouseover and mouseout events do.

Handling them should showcase an interesting behavior, so let's do that.

Consider the code below:

var divElement = document.querySelector('div');

divElement.onmouseover = function() {
   this.classList.toggle('pink');
}

This time we're not handling mouseenter, but rather its refiring counterpart, i.e. mouseover.

A span in a div

When you move the pointer into the <div> element, just as before, its background changes to pink. But if you now go over to the <span> element, you'll see the background color going back to #ddd.

Then when you move out of <span>, still staying within the <div>, the background goes to pink once again. Go again inside the <span>, and the background changes yet again.

What in this world is happening over here?

Well, this sure might sound and seem confusing, but if you take a closer look at it, you'll find that everything works pretty sensibly.

Making sense of the mouseover example above

Recall our earlier definition: 'The mouseover event fires on an element each time the mouse pointer is brought over it or one of its descendants.'

In the last example above, when we brought the pointer into the <div> from outside, its mouseover event fired and consequently the background color changed to pink.

Then when we moved into the <span> element, the mouseover event fired again on <div>, since we moved from <div> to its child <span>. Likewise, the background changed back to #ddd. (This behavior might seem counter-intuitive, but it's just that way!)

Moving out of the <span> caused another mouseover event, since we moved from a child of <div> to the <div> itself, resulting in the background changing again to pink.

Finally, going into the <span> again fired yet another mouseover event, thereby changing the background back to #ddd.

This change keeps on happening as long as we keep on moving the pointer between the <div> and <span> elements. In each movement, we either move from a descendant to the main element, or from the main element to a descendant, which ultimately dispatches a mouseover event on <div>.

A similar result could be achieved by handling mouseout alone (without a corresponding mouseover handler).

This can be seen below:

var divElement = document.querySelector('div');

divElement.onmouseout = function() {
   this.classList.toggle('pink');
}
A span in a div

Simple?

We could even handle both these mouse events together, slightly modifying the classList statements in their handlers, thereby giving us a pretty interesting result to interact with.

Have a look at the code below:

var divElement = document.querySelector('div');

divElement.onmouseover = function() {
   this.classList.add('pink');
}

divElement.onmouseout = function() {
   this.classList.remove('pink');
}

As always, try interacting with the live result shown below:

A span in a div

This example showcases one extremely important thing: mouseover fires after a mouseout event on the same element.

Had it been the other way round, i.e. mouseout firing after mouseover, then taking the pointer over the <span> would have first triggered mouseover, making the background pink (which it already is), and then triggered mouseout, making the background #ddd.

However, as you can see in the live result above, this is NOT the case.

Instead, when we take the pointer inside the <span>, the background remains pink. And that's because going over <span> first triggers mouseout, making the background #ddd, and the triggers mouseover making the background pink again.

As a rule of thumb, remember that:

For a given element, the mouseover and mouseout events fire on it each time the mouse pointer is brought into the element or one of its descendants, or brought out of it or one of its descendants, respectively.

Using the target property

One useful thing about these events is that when either of them fires, their target property points to the element which specifically caused the event.

If you're not familiar with the target property of event objects, refer to JavaScript Events — Event Objects.

For example, in the example above using mouseover, when we move into <span>, the target of the fired mouseover event becomes this <span> element. However, the target of the preceding mouseout event becomes the <div> element.

Let's see an actual example of mouseover and mouseout using the target property.

Consider the following code:

<p><code>mouseover</code> triggered on <code id="c1"></code></p>
<p><code>mouseout</code> triggered on <code id="c2"></code></p>

<div>A <span>span</span> in a div</div>
var divElement = document.querySelector('div');
var c1Element = document.querySelector('#c1');
var c2Element = document.querySelector('#c2');

divElement.onmouseover = function(e) {
   this.classList.add('pink');
   c1Element.textContent = e.target.nodeName;
}

divElement.onmouseout = function(e) {
   this.classList.remove('pink');
   c2Element.textContent = e.target.nodeName;
}

Here's the live result to interact with:

mouseover triggered on:

mouseout triggered on:

A span in a div

As we interact with the <div> element by moving the pointer around inside it, we display the tag name of the target of each of the events, mouseover and mouseout.

Let's consider a more practical example.

Say you have the following HTML that consists of three <div>s nested inside each other:

A div
A sub div
A sub-sub div

Your task is to focus the <div> element, by showing an outline around it, that is currently below the mouse pointer. When the pointer leaves that <div>, the outline must be removed.

So how will you approach this problem?

If we didn't have the mouseover event in our inventory, we would have to handle mouseenter on every element in the document, which could become really complex. The go-to solution for this problem is to handle the mouseover and mouseout events on the top-most element.

So going with this approach, we'll solve the aforementioned task by handling mouseover and mouseout on the <body> element. The result: our problem solved!

document.body.onmouseover = function(e) {
   e.target.style.outline = '2px dashed red';
}

document.body.onmouseout = function(e) {
   e.target.style.outline = 'none';
}
A div
A sub div
A sub-sub div

Live Example

Mouse moving

The last event left to be explored in this chapter is mousemove.

As the name suggests, the mousemove event fires continuously while the pointer moves over an element. The moment the pointer's movement is stopped, mousemove also stops firing.

Now generally, when mousemove is handled on any element, the coordinates of the mouse pointer are used inside the handler.

Consider the simple example below:

<p>Coordinates of pointer: (<code id="x-pos"></code>, <code id="y-pos"></code>)</p>

<div>Move the mouse here</div>

Coordinates of pointer: (, )

Move the mouse here

Our aim is that as we move the pointer inside the <div>, we shall display the coordinates of the pointer in the respective <code> elements.

var divElement = document.querySelector('div');
var xPosElement = document.getElementById('x-pos');
var yPosElement = document.getElementById('y-pos');

divElement.onmousemove = function(e) {
   xPosElement.textContent = e.clientX;
   yPosElement.textContent = e.clientY;
}

Live Example

And with this we are done with mouse events!