Hoppa till huvudinnehåll

Mouse, Touch and Pointer events

· 5 min att läsa
Filip Tammergård
Software Engineer at Einride

I recently stumbled upon a bug related to the subtle differences between mouse, touch and pointer events. The bug made me confused—and curious. What is actually the difference between these events?

I opened an issue for the bug that you can check out if you're also curious—but before trying to understand the bug, we need to set the stage with some knowledge around different types of events.

Mouse events

Historically, the mouse was the only type of input device. Because of that, the only type of input event was the mouse event. That is the baseline.

Then came touch devices. Touching a screen with a finger can partly be represented with a mouse event, but for example when it comes to multi-touch, such representation is not possible. We needed touch events!

Touch events

For compatibility with the existing mouse-based web, touch events were configured by user agents to fire subsequent mouse events, called "compatibility mouse events". The compatibility mouse events came with an ambiguity problem. If you for some reason need to listen to both events to handle mouse clicks and touches separately, a mouse click only fires the mouse event, while a touch fires both. It became difficult to know whether a mouse event was fired by an actual mouse click or a touch.

User agents were configured to make use of preventDefault to solve the ambiguity problem.

From the spec for touch events:

If the preventDefault method of touchstart or touchmove is called, the user agent should not dispatch any mouse event that would be a consequential result of the the prevented touch event.

Then came pens—a device that provides yet another type of device input. Should pen events be introduced? That would lead to the need of having to listen to yet another event type, and it would probably also require firing compatibility mouse events that yet again would lead to ambiguity problems.

Instead of going further down this rabbit hole, a new more abstract type of event was introduced. It's called pointer events.

Pointer events

A pointer event is not tied to any specific input device, but represents any type of device input. This means you can listen to pointer events only, and all types of device input is taken care of. If you want to handle types of device input differently, the pointer event provides a way of inspecting the device type that produced the pointer event.

Yet again, compatibility mouse events are fired to support the existing web. And yet again preventDefault was utilized to make it possible to opt out of firing these compatibility mouse events.

Playground

Let's play around with this knowledge for a while! Use the checkboxes to call preventDefault on different types of events. Then click the button and see what happens with the event counts.

mousedown event count: 0

touchstart event count: 0

pointerdown event count: 0

We can see a few things in action here:

  • When no preventDefault is called, clicking the button with a mouse results in one mousedown event and one pointerdown event.
  • When no preventDefault is called, clicking the button with a finger on a touch device results in one touchstart, one mousedown event and one pointerdown event.
  • When preventDefault is called on the touchstart event, no mousedown event is fired when clicking the button with a finger on a touch device.
  • When preventDefault is called on the pointerdown event, no mousedown event is fired when clicking the button with a mouse, or when clicking the button with a finger on a touch device.
  • Calling preventDefault on the mousedown event has no effect on any of the event counts compared to not calling it.

Back to the bug

A thorough description of the bug can be found in the GitHub issue. I'll do a quick summary here.

I had a date picker that closed when I clicked outside. But when I clicked on a menu button, the menu was opened while the date picker was not closed. In other words, after clicking the date picker and then the menu button, both of them were left open.

Turns out that the date picker was closed on mousedown and touchstart events. Changing it to close on pointerdown solved the issue. That is an easy fix, but it's not satisfying to solve a bug without understanding what caused the problem and why the fix solves it.

The menu button called preventDefault on the pointerdown event, to prevent the menu button from being focused when clicked and hence make it possible to give focus to the menu content without competition.

As we have learned, calling preventDefault on a pointerdown event also prevents compatibility mouse events from being fired. Since the date picker listened to mousedown and touchstart to close, and none of these events were called, it didn't close.

Since a pointerdown event was called, the date picker did close when listening to pointerdown instead of mousedown and touchstart.

Mouse and touch events are still used in many places, probably partly due to the fact that they have better browser support. But support for pointer events is really good now and most of the web would probably benefit from using pointer events only.

Conclusion

  • While mouse and touch events are tied to a specific type of device, pointer events are hardware agnostic.
  • Pointer events can be used instead of other types of events to avoid duplicated event handling logic and problems related to compatibility mouse events.

References