I\'m developing a webapp (not a website with pages of interesting text) with a very different interface for touch (your finger hides the screen when you click) and mouse (re
the main problem I see here is that most touch devices fire a mouse event along with the coresponding touch one (touchstart -> mousedown, touchmove -> mousemove, etc). For the keyboard only ones, at last for the modern ones, they have a generic browser so you can't even detect the presence of the MouseEvent class.
The less painful solution here would be, in my opinion, to display a menu at launch (with 'alt' management for the keyboard only users) and maybe storing the choice with localStorage/cookies/serverside or else to keep the same choice the next time the visitor comes.
The main trouble is that you have the following different classes of devices/use cases:
What's worse, is that one can transition from some of these classes to others (plugs in a mouse, connects to keyboard), or a user may APPEAR to be on a normal laptop until they reach out and touch the screen.
You are correct in assuming that the presence of event constructors in the browser is not a good way to move forward (and it is somewhat inconsistent). Additionally, unless you are tracking a very specific event or only trying to rule out a few classes above, using events themselves isn't full proof.
For example, say you've discovered that a user have emitted a real mousemove (not the false one from touch events, see http://www.html5rocks.com/en/mobile/touchandmouse/).
Then what?
You enable hover styles? You add more buttons?
Either way you are increasing time to glass because you have to wait for an event to fire.
But then what happens when your noble user decides wants to unplug his mouse and go full touch.. do you wait for him to touch your now crammed interface, then change it right after he's made the effort to pinpoint your now crowded UI?
In bullet form, quoting stucox at https://github.com/Modernizr/Modernizr/issues/869#issuecomment-15264101
- We want to detect the presence of a mouse
- Ae probably can't detect before an event is fired
- As such, what we're detecting is if a mouse has been used in this session — it won't be immediately from page load
- We probably also can't detect that there isn't a mouse — it'd be undefined until true (I think this makes more sense than setting it false until proven)
- And we probably can't detect if a mouse is disconnected mid-session — that'll be indistinguishable from the user just giving up with their mouse
An aside: the browser DOES know when a user plugs in a mouse/connects to a keyboard, but doesn't expose it to JavaScript.. dang!
This should lead you to the following:
Tracking the current capabilities of a given user is complex, unreliable, and of dubious merit
The idea of progressive enhancement applies quite well here, though. Build an experience that works smoothly no matter the context of the user. Then make assumptions based on browser features/media queries to add functionality that will be relative in the assumed context. Presence of a mouse is just one of the multitudes of ways in which different users on different devices experience your website. Create something with merit at its kernel and don't worry too much about how people click the buttons.
When Media Queries Level 4 is available in browsers, we will be able to use the "pointer" and "hover" queries to detect devices with a mouse.
If we really want to communicate that information to Javascript, we could use a CSS query to set specific styles according to the device type, and then use getComputedStyle
in Javascript to read that style, and derive the original device type from it.
But a mouse can be connected or unplugged at any time, and the user may be wanting to switch between touch and mouse. So we may need to detect this change, and offer to change interface or do so automatically.
How about listening for a mousemove event on the document. Then until you hear that event you assume that the device is touch or keyboard only.
var mouseDetected = false;
function onMouseMove(e) {
unlisten('mousemove', onMouseMove, false);
mouseDetected = true;
// initializeMouseBehavior();
}
listen('mousemove', onMouseMove, false);
(Where listen
and unlisten
delegate to addEventListener
or attachEvent
as appropriate.)
Hopefully this wouldn't lead to too much visual jank, it would suck if you need massive re-layouts based on mode...
I ran into the same issue, where a single touch was also registered as a click. After I read through the comments of top voted answers, i came up with my own solution:
var body = document.getElementsByTagName('body')[0];
var mouseCount = 0;
// start in an undefined state
// (i use this to blend in elements once we decide what input is used)
var interactionMode = 'undefined';
var registerMouse = function() {
// count up mouseCount every time, the mousemove event is triggered
mouseCount++;
// but dont set it instantly.
// instead wait 20 miliseconds (seems to be a good value for multiple move actions),
// if another mousemove event accoures switch to mouse as interaction
setTimeout(function() {
// a touch event triggers also exactly 1 mouse move event.
// So only if mouseCount is higher than 1 we are really moving the cursor by mouse.
if (mouseCount > 1) {
body.removeEventListener('mousemove', registerMouse);
body.removeEventListener('touchend', registerTouch);
interactionMode = 'mouse';
console.log('now mousing');
listenTouch();
}
// set the counter to zero again
mouseCount = 0;
}, 20);
};
var registerTouch = function() {
body.removeEventListener('mousemove', registerMouse);
body.removeEventListener('touchend', registerTouch);
interactionMode = 'touch';
console.log('now touching');
mouseCount = 0;
listenMouse();
};
var listenMouse = function() {
body.addEventListener("mousemove", registerMouse);
};
var listenTouch = function() {
body.addEventListener("touchend", registerTouch);
};
listenMouse();
listenTouch();
// after one second without input, assume, that we are touching
// could be adjusted to several seconds or deleted
// without this, the interactionMode would stay 'undefined' until first mouse or touch event
setTimeout(function() {
if (!body.classList.contains('mousing') || body.classList.contains('touching')) {
registerTouch();
}
}, 1000);
/* fix, so that scrolling is possible */
html,
body {
height: 110%;
}
Mouse or touch me
The only problem i found is, that you have to be able to scroll, to properly detect a touch event. a single tab(touch) might make problems.
As of 2018 there is a good and reliable way to detect if a browser has a mouse (or similar input device): CSS4 media interaction features which are now supported by almost any modern browser (except IE 11 and special mobile browsers).
W3C:
The pointer media feature is used to query the presence and accuracy of a pointing device such as a mouse.
See the following options:
/* The primary input mechanism of the device includes a
pointing device of limited accuracy. */
@media (pointer: coarse) { ... }
/* The primary input mechanism of the device
includes an accurate pointing device. */
@media (pointer: fine) { ... }
/* The primary input mechanism of the
device does not include a pointing device. */
@media (pointer: none) { ... }
/* Primary input mechanism system can
hover over elements with ease */
@media (hover: hover) { ... }
/* Primary input mechanism cannot hover
at all or cannot conveniently hover
(e.g., many mobile devices emulate hovering
when the user performs an inconvenient long tap),
or there is no primary pointing input mechanism */
@media (hover: none) { ... }
/* One or more available input mechanism(s)
can hover over elements with ease */
@media (any-hover: hover) { ... }
/* One or more available input mechanism(s) cannot
hover (or there are no pointing input mechanisms) */
@media (any-hover: none) { ... }
Media queries can also be used in JS:
if(window.matchMedia("(any-hover: none)").matches) {
// do sth
}
Related:
W3 documentation: https://www.w3.org/TR/mediaqueries-4/#mf-interaction
Browser support: https://caniuse.com/#search=media%20features
Similar problem: Detect if a client device supports :hover and :focus states