Mouse events

Almost every interface you build reacts to a pointer moving and clicking. This chapter takes the mouse events you’ve already met and fills in the parts that trip people up: exactly which events fire and in what order, how to tell the left button from the right, which modifier keys were held down, and the two coordinate systems every mouse event carries.

One thing to keep in mind before the details. These events don’t only come from an actual mouse. Phones, tablets, and trackpads generate them too. When you tap a touchscreen, the browser fires a matching set of mouse events so that pages written for a mouse keep working. So “mouse event” is really about a pointing action, not a physical mouse.

Mouse event types

You’ve seen several of these already. Here’s the full cast for this chapter.

mousedown / mouseup — a mouse button is pressed down over an element, then released over it.
mouseover / mouseout — the pointer moves onto an element, then leaves it.
mousemove — fires for every move of the pointer while it’s over an element. A single drag across a box can fire this dozens of times.
click — fires after a mousedown and then a mouseup land on the same element, and only for the left button.
dblclick — fires after two clicks on the same element inside a short window. You’ll rarely reach for it these days.
contextmenu — fires when the right button is pressed. But a keyboard menu key (or a two-finger tap) opens the context menu too and also fires this event, so treat it as “the menu is being requested,” not strictly “right mouse button.”

There are more mouse-related events (the mouseenter/mouseleave pair, drag events, and so on) that later chapters cover. The list above is enough to reason about a normal click.

Events order

A single user action often triggers a sequence of events, not one. Press and release the left button, and three events fire in a row.

When one action produces several events, the browser fires them in a fixed order. For a left click that order is always mousedownmouseupclick.

timebutton pressedmousedownbutton releasedmouseupclicksynthesized aftera matching up
A single left click, expanded on a timeline. The physical press fires mousedown; the release fires mouseup; then, because both landed on the same element, the browser synthesizes click.

A double click layers on top of this. Each of the two clicks fires its own full mousedownmouseupclick sequence, and only after the second one does dblclick fire. So a double click is a fairly busy stream of events.

To watch this live, wire one handler to several events on a single target and print each one into a log as it fires. Click the box below with the left button and you’ll see the mousedown → mouseup → click trio arrive. Try the right button for mousedown → mouseup → contextmenu. Double-click to watch two full sequences land before dblclick. The log also prints each event’s button value (explained next) and drops a divider whenever more than a second passes between events.

interactiveLog the events one action fires

Mouse button

Every click-related event carries a button property telling you which physical button was involved.

You usually don’t need it for click or contextmenu: click only ever fires for the left button, and contextmenu is tied to the right button (or the menu key). The property earns its keep on mousedown and mouseup, which fire for any button. There, event.button is how you separate a right-button press from a left-button press.

The possible values:

Button state event.button
Left button (primary) 0
Middle button (auxiliary) 1
Right button (secondary) 2
X1 button (back) 3
X2 button (forward) 4
021leftrightwheel = 1
event.button maps to a physical button. Most mice only expose left (0) and right (2); the middle wheel-click is 1, and the two side buttons are 3 and 4.

Most mice only have left and right buttons, so in practice you’ll see 0 or 2. Touch devices produce equivalent events when someone taps.

Modifiers: shift, alt, ctrl and meta

Every mouse event also records which modifier keys were held while it fired. Four boolean properties:

  • event.shiftKeyShift
  • event.altKeyAlt (or Opt on Mac)
  • event.ctrlKeyCtrl
  • event.metaKeyCmd on Mac

Each is true if that key was down at the moment of the event. That lets you build shortcuts like Alt+Shift+click. This button only reacts when both are held:

<button id="unlock">Alt+Shift+Click to unlock</button>

<script>
  unlock.onclick = function(event) {
    if (event.altKey && event.shiftKey) {
      alert('Panel unlocked!');
    }
  };
</script>

Try it below. Hold different combinations of keys while you click and watch the four boolean flags flip. The button only unlocks when both Alt and Shift are down at the moment of the click:

interactiveRead the modifier flags on a click

Coordinates: clientX/Y, pageX/Y

Every mouse event reports where the pointer was, in two coordinate systems at once:

  1. Window-relativeevent.clientX and event.clientY, measured from the top-left of the visible area.
  2. Document-relativeevent.pageX and event.pageY, measured from the top-left of the whole document.

The full story is in the Coordinates chapter. The short version: pageX/Y are anchored to the document, so scrolling doesn’t change them for a given spot in the content. clientX/Y are measured from the current viewport, so they do change as you scroll, the same way position:fixed stays put relative to the window.

at top of pagewindow top = doc toppointerpageY 720clientY 720scrolled down 500pxscrolled out of viewwindow toppointerpageY 720clientY 220
The same pointer position, before and after scrolling. pageY is pinned to the document top and stays 720; clientY is measured from the visible top edge and drops as the content scrolls up under the pointer.

Picture a 500×500 window. If the pointer sits in the top-left corner, clientX and clientY are both 0, no matter how far the page is scrolled. In the center they’re both 250, again independent of scroll. That’s the “fixed to the window” behavior.

To see clientX/Y change as you move, print them from a mousemove handler. Glide the pointer across the pad below — the readout updates on every move, and a dot tracks your position inside the box:

interactiveLive pointer coordinates

Preventing selection on mousedown

Double clicks have an annoying side effect: they select text. Double-click the word below and the browser highlights it, right alongside whatever your handler does:

<span ondblclick="alert('dblclick')">Double-click me</span>

Pressing the left button and dragging does the same thing — it starts a text selection, which is usually unwanted inside interactive widgets like a custom slider or a sortable list.

There are several ways to stop selection, covered in Selection and Range. For this specific problem the cleanest fix is to cancel the browser’s default action on mousedown, which is what starts a selection. Return false from the handler:

Before...
<b ondblclick="alert('Click!')" onmousedown="return false">
  Double-click me
</b>
...After

Now double-clicking the bold text won’t highlight it, and press-and-drag on it won’t begin a selection either.

Compare the two directly. Double-click (or press and drag across) each label below. The first selects text the usual way; the second cancels the default on mousedown, so no highlight appears:

interactivemousedown return false blocks selection
mousedowndefault: start selectionreturn false cancels thisno highlight
Selection begins on mousedown. Cancel the default action there and neither the double-click highlight nor a drag-selection can start.

The text inside is still selectable — you just can’t begin a selection on the element itself. A user can start selecting before or after it and drag across, which is normally fine.

Summary

Mouse events hand you a consistent set of properties:

  • Which button: event.button (0 left, 1 middle, 2 right, 3/4 side buttons). Needed mainly on mousedown/mouseup, which fire for any button. There’s also a plural event.buttons bitmask for combinations, rarely needed.
  • Modifier keys (true when held): event.altKey, event.ctrlKey, event.shiftKey, and event.metaKey (Mac’s Cmd). For a Ctrl shortcut, check if (event.ctrlKey || event.metaKey) so Mac users get Cmd, and always leave a path for devices with no keyboard.
  • Window-relative coordinates: event.clientX / event.clientY, unaffected by scroll.
  • Document-relative coordinates: event.pageX / event.pageY, anchored to the document.

Events from one action fire in a fixed order — mousedownmouseupclick for a left click. The default action of mousedown is to begin text selection; cancel it when that gets in the way of your interface.

Next up: the events that track the pointer moving across elements, and how to follow what’s underneath it.