JavaScript in the browser uses an event-driven programming model.

Usually things start by responding to an event.

The event could be the DOM is loaded, or an asynchronous request that finishes fetching, or a user clicking an element or scrolling the page, or the user types on the keyboard.

In this lesson we’ll analyze user activated events coming from the mouse or touch devices, like mouse clicks or tap events on mobile devices.


Event handlers

You can respond to any event using an Event Handler, which is a function that’s called when an event occurs.

You can register multiple handlers for the same event, and they will all be called when that event happens.

JavaScript offer three ways to register an event handler:

Inline event handlers

This style of event handlers is very rarely used today, due to its constraints, but it was the only way in the JavaScript early days:

<a href="#" onclick="alert('link clicked')">A link</a>

DOM on-event handlers

This is common when an object has at most one event handler, as there is no way to add multiple handlers in this case:

document.querySelector('a').onclick = () => {
  alert('link clicked')
}

You can check if an handler is already assigned to an element using

if ('onclick' in document.querySelector('a')) {
  alert('onclick handler already registered')
}

Using addEventListener()

This is the modern way. This method allows to register as many handlers as we need, and it’s the one you will find mostly used in the wild:

window.addEventListener('load', () => {
  //window loaded
})

Listening on different elements

Sometimes addEventListener is called on window, sometimes on a specific DOM element. Why?

It’s a matter of determining how large of a net you want when you are looking at intercepting events.

You can listen on window to intercept “global” events, like the usage of the keyboard, and you can listen on specific elements to check events happening specifically on them, like a mouse click on a button.

The Event object passed to the event handler

An event handler gets an Event object as the first parameter:

const link = document.getElementById('my-link')
link.addEventListener('click', event => {
  // link clicked
})

This object contains a lot of useful properties and methods, like:

  • target, the DOM element that originated the event
  • type, the type of event
  • stopPropagation(), called to stop propagating the event in the DOM

and more. You can see the full list here.

Each specific kind of events, like a mouse click, a touch event, a keyboard event, all implement an event that extend this base Event object. We are talking about mouse and touch events in this lesson, so we are interested in these 2:

Mouse events

When looking at mouse events we have the ability to interact with

  • mousedown the mouse button was pressed
  • mouseup the mouse button was released
  • click a click event
  • dblclick a double click event
  • mousemove when the mouse is moved over the element
  • mouseover when the mouse is moved over an element or one of its child elements
  • mouseenter when the mouse is moved over an element. Similar to mouseover but does not bubble (more on this soon!)
  • mouseout when the mouse is moved out of an element, and when the mouse enters a child elements
  • mouseleave when the mouse is moved out of an element. Similar to mouseout but does not bubble (more on this soon!)
  • contextmenu when the context menu is opened, e.g. on a right mouse button click

Events overlap. When you track a click event, it’s like tracking a mousedown followed by a mouseup event. In the case of dblclick, click is also fired two times.

mousedown, mousemove and mouseup can be used in combination to track drag-and-drop events.

Be careful with mousemove, as it fires many times during the mouse movement. We need to apply throttling, which is something we’ll talk more when we’ll analyze scrolling.

When inside an eventh handler we have access to lots of properties.

For example on a mouse event we can check which mouse button was pressed by checking the button property of the event object:

const link = document.getElementById('my-link')
link.addEventListener('mousedown', event => {
  // mouse button pressed
  console.log(event.button) //0=left, 2=right
})

Here are all the properties we can use:

  • altKey true if alt key was pressed when the event was fired
  • button if any, the number of the button that was pressed when the mouse event was fired (usually 0 = main button, 1 = middle button, 2 = right button). Works on events caused by clicking the button (e.g. clicks)
  • buttons if any, a number indicating the button(s) pressed on any mouse event.
  • clientX / clientY the x and y coordinates of the mouse pointer relative to the browser window, regardless of scrolling
  • ctrlKey true if ctrl key was pressed when the event was fired
  • metaKey true if meta key was pressed when the event was fired
  • movementX / movementY the x and y coordinates of the mouse pointer relative to the position of the last mousemove event. Used to track the mouse velocity while moving it around
  • region used in the Canvas API
  • relatedTarget the secondary target for the event, for example when moving
  • screenX / screenY the x and y coordinates of the mouse pointer in the screen coordinates
  • shiftKey true if shift key was pressed when the event was fired

Touch events

Touch events are those events that are triggered when viewing the page on a mobile device, like a smartphone or a tablet.

They allow you to track multitouch events.

We have 4 touch events:

  • touchstart a touch event has started (the surface is touched)
  • touchend a touch event has ended (the surface is no longer touched)
  • touchmove the finger (or whatever is touching the device) moves over the surface
  • touchcancel the touch event has been cancelled

Every time a touch event occurs we are passed a touch event:

const link = document.getElementById('my-link')
link.addEventListener('touchstart', event => {
  // touch event started
})

Here are all the properties we can access on that event

  • identifier an unique identifier for this specific event. Used to track multi-touch events. Same finger = same identifier.
  • clientX / clientY the x and y coordinates of the mouse pointer relative to the browser window, regardless of scrolling
  • screenX / screenY the x and y coordinates of the mouse pointer in the screen coordinates
  • pageX / pageY the x and y coordinates of the mouse pointer in the page coordinates (including scrolling)
  • target the element touched

Event bubbling and event capturing

Bubbling and capturing are the 2 models that events use to propagate.

Suppose you DOM structure is

<div id="container">
  <button>Click me</button>
</div>

You want to track when users click on the button, and you have 2 event listeners, one on button, and one on #container. Remember, a click on a child element will always propagate to its parents, unless you stop the propagation (see later).

Those event listeners will be called in order, and this order is determined by the event bubbling/capturing model used.

Bubbling means that the event propagates from the item that was clicked (the child) up to all its parent tree, starting from the nearest one.

In our example, the handler on button will fire before the #container handler.

Capturing is the opposite: the outer event handlers are fired before the more specific handler, the one on button.

By default all events bubble.

You can choose to adopt event capturing by applying a third argument to addEventListener, setting it to true:

document.getElementById('container').addEventListener(
  'click',
  () => {
    //window loaded
  },
  true
)

Note that first all capturing event handlers are run.

Then all the bubbling event handlers.

The order follows this principle: the DOM goes through all elements starting from the Window object, and goes to find the item that was clicked. While doing so, it calls any event handler associated to the event (capturing phase).

Once it reaches the target, it then repeats the journey up to the parents tree until the Window object, calling again the event handlers (bubbling phase).

Stopping the propagation

An event on a DOM element will be propagated to all its parent elements tree, unless it’s stopped.

<html>
  <body>
    <section>
      <a id="my-link" ...>

A click event on a will propagate to section and then body.

You can stop the propagation by calling the stopPropagation() method of an Event, usually at the end of the event handler:

const link = document.getElementById('my-link')
link.addEventListener('mousedown', event => {
  // process the event
  // ...

  event.stopPropagation()
})