Given the complexity of the issue, I thought it was worth detailing the issues and edge cases involved in any potential solution.
The issues:
1 – Different implementations of touch events across devices and browsers. What works for some will definitely not work for others. You only need to glance at those patrickhlauke resources to get an idea of how differently the process of tapping a touch-screen is currently handled across devices and browsers.
2 – The event handler gives no clue as to its initial trigger. You are also absolutely right in saying that the event
object is identical (certainly in the vast majority of cases) between mouse events dispatched by interaction with a mouse, and mouse events dispatched by a touch interaction.
3 – Any solution to this problem which covers all devices could well be short-lived as the current W3C Recommendations do not go into enough detail on how touch/click events should be handled (https://www.w3.org/TR/touch-events/), so browsers will continue to have different implementations. It also appears that the Touch Events standards document has not changed in the past 5 years, so this isn’t going to fix itself soon. https://www.w3.org/standards/history/touch-events
4 – Ideally, solutions should not use timeouts as there is no defined time from touch event to mouse event, and given the spec, there most probably won’t be any time soon. Unfortunately, timeouts are almost inevitable as I will explain later.
A future solution:
In the future, the solution will probably be to use Pointer Events
instead of mouse / touch events as these give us the pointerType
(https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), but unfortunately we’re not there yet in terms of an established standard, and so cross-browser compatibility (https://caniuse.com/#search=pointer%20events) is poor.
How do we solve this at the moment
If we accept that:
- You can’t detect a touchscreen (http://www.stucox.com/blog/you-cant-detect-a-touchscreen/)
- Even if we could, there’s still the issue of non-touch events on a touch capable screen
Then we can only use data about the mouse event itself to determine its origin. As we’ve established, the browser doesn’t provide this, so we need to add it ourselves. The only way to do this is using the touch events which are triggered around the same time as the mouse event.
Looking at the patrickhlauke resources again, we can make some statements:
mouseover
is always followed by the click eventsmousedown
mouseup
andclick
– always in that order. (Sometimes separated by other events). This is backed up by the W3C recommendations: https://www.w3.org/TR/touch-events/.- For most devices / browsers, the
mouseover
event is always preceded by eitherpointerover
, its MS counterpartMSPointerOver
, ortouchstart
- The devices / browsers whose event order begins with
mouseover
have to be ignored. We can’t establish that the mouse event was triggered by a touch event before the touch event itself has been triggered.
Given this, we could set a flag during pointerover
, MSPointerOver
, and touchstart
, and remove it during one of the click events. This would work well, except for a handfull of cases:
event.preventDefault
is called on one of the touch events – the flag will never be unset as the click events will not be called, and so any future genuine click events on this element would still be marked as a touch event- if the target element is moved during the event. The W3C Recommendations state
If the contents of the document have changed during processing of the
touch events, then the user agent may dispatch the mouse events to a
different target than the touch events.
Unfortunately this means that we will always need to use timeouts. To my knowledge there is no way of either establishing when a touch event has called event.preventDefault
, nor understanding when the touch element has been moved within the DOM and the click event triggered on another element.
I think this is a fascinating scenario, so this answer will be amended shortly to contain a recommended code response. For now, I would recommend the answer provided by @ibowankenobi or the answer provided by @Manuel Otto.