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.
Properties | Purpose |
---|---|
screenX / screenY | Specify the x and y coordinates of the pointer relative to the screen, respectively. |
clientX / clientY | Specify the x and y coordinates of the pointer relative to the browser window, respectively. |
pageX / pageY | Specify the x and y coordinates of the pointer relative to the webpage, respectively. |
x / y | Alias of clientX and clientY , respectively. |
altKey | Return a Boolean specifying whether or not the Alt key is pressed. |
metaKey | Return 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. |
ctrlKey | Return a Boolean specifying whether or not the Ctrl key is pressed. |
shiftKey | Return a Boolean specifying whether or not the Shift key is pressed. |
button | Returns a number representing the last button currently pressed on the mouse. |
buttons | Returns a number representing all of the buttons currently pressed on the mouse. |
relatedTarget | Returns the target related to the current event. |
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:
mousedown
— indicates that the mouse button has gone down.mouseup
— indicates that the mouse button has gone up.click
— our 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
.
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.
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%)
}
}
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);
}
Here's what's happening:
- The moment a click is made inside
#mouse-area
, the position of the mouse pointer is determined, viae.clientX
ande.clientY
. - Then using this position relative to the client window, the corresponding values for the
left
andtop
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). - Once the correct position for a circle is determined, it's created via the call to
createCircle()
. 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?
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);
}
}
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 themousedown
event. - The
mouseup
event's handler serves to remove the element stored insidepreviousCircleElement
, if there happens to be one (which is checked with a simpleif
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');
}
}
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
.
preventDefault()
and activation behaviors, refer to JavaScript Events — Event Objects — preventDefault()
.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:
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.
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:
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
.
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');
}
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:
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:
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.
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:
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:
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';
}
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: (,
)
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;
}
And with this we are done with mouse events!