Detecting that the browser has no mouse and is touch-only

60,773

Solution 1

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 something
}

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

Solution 2

The main trouble is that you have the following different classes of devices/use cases:

  1. Mouse and keyboard (desktop)
  2. Touch only (phone/tablet)
  3. Mouse, keyboard, and touch (touch laptops)
  4. Touch and keyboard (bluetooth keyboard on tablet)
  5. Mouse only (Disabled user/browsing preference)
  6. Keyboard only (Disabled user/browsing preference)
  7. Touch and mouse (ie hover events from Galaxy Note 2 pen)

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 has 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
  • We probably can't detect it 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.

Solution 3

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...

Solution 4

@Wyatt's answer is great and gives us a lot to think about.

On my case, I chose to listen for the first interaction, to only then set a behavior. So, even if the user has a mouse, I will treat as touch device if first interaction was a touch.

Considering the given order in which events are processed:

  1. touchstart
  2. touchmove
  3. touchend
  4. mouseover
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

We can assume that if mouse event gets triggered before touch, it is a real mouse event, not an emulated one. Example (using jQuery):

$(document).ready(function() {
    var $body = $('body');
    var detectMouse = function(e){
        if (e.type === 'mousedown') {
            alert('Mouse interaction!');
        }
        else if (e.type === 'touchstart') {
            alert('Touch interaction!');
        }
        // remove event bindings, so it only runs once
        $body.off('mousedown touchstart', detectMouse);
    }
    // attach both events to body
    $body.on('mousedown touchstart', detectMouse);
});

That worked for me

Solution 5

It's only possible to detect if a browser is touch capable. There is no way to know if it actually has a touch screen or a mouse connected.

One can prioritize the use though by listening to touch event instead of mouse event if touch capability is detected.

To detect touch capability cross-browser:

function hasTouch() {
    return (('ontouchstart' in window) ||       // html5 browsers
            (navigator.maxTouchPoints > 0) ||   // future IE
            (navigator.msMaxTouchPoints > 0));  // current IE10
}

Then one can use this to check:

if (!hasTouch()) alert('Sorry, need touch!);

or to choose which event to listen to, either:

var eventName = hasTouch() ? 'touchend' : 'click';
someElement.addEventListener(eventName , handlerFunction, false);

or use separate approaches for touch vs. non-touch:

if (hasTouch() === true) {
    someElement.addEventListener('touchend' , touchHandler, false);

} else {
    someElement.addEventListener('click' , mouseHandler, false);

}
function touchHandler(e) {
    /// stop event somehow
    e.stopPropagation();
    e.preventDefault();
    window.event.cancelBubble = true;
    // ...
    return false; // :-)
}
function mouseHandler(e) {
    // sorry, touch only - or - do something useful and non-restrictive for user
}

For mouse one can only detect if the mouse is being used, not if it exists or not. One can setup a global flag to indicate that mouse was detected by usage (similar to an existing answer, but simplified a bit):

var hasMouse = false;

window.onmousemove = function() {
    hasMouse = true;
}

(one cannot include mouseup or mousedown as these events can also be triggered by touch)

Browsers restricts access to low-level system APIs which is needed to be able to detect features such as hardware capabilities of the system it's being used on.

There is the possibility to perhaps write a plugin/extension to access these but via JavaScript and DOM such detection is limited for this purpose and one would have to write a plugin specific for the various OS platforms.

So in conclusion: such detection can only be estimated by a "good guess".

Share:
60,773

Related videos on Youtube

nraynaud
Author by

nraynaud

Remote Software Engineer for hire.

Updated on March 20, 2022

Comments

  • nraynaud
    nraynaud about 2 years

    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 (relies heavily on hover preview). How can I detect that my user has no mouse to present him the right interface? I plan to leave a switch for people with both mouse and touch (like some notebooks).

    The touch event capability in the browser doesn't actually mean the user is using a touch device (for example, Modernizr doesn't cut it). The code that correctly answers the question should return false if the device has a mouse, true otherwise. For devices with mouse and touch, it should return false (not touch only)

    As a side note, my touch interface might also be suitable for keyboard-only devices, so it's more the lack of mouse I'm looking to detect.

    To make the need more clear, here is the API that I'm looking to implement:

    // Level 1
    
    
    // The current answers provide a way to do that.
    hasTouch();
    
    // Returns true if a mouse is expected.
    // Note: as explained by the OP, this is not !hasTouch()
    // I don't think we have this in the answers already, that why I offer a bounty
    hasMouse();
    
    // Level 2 (I don't think it's possible, but maybe I'm wrong, so why not asking)
    
    // callback is called when the result of "hasTouch()" changes.
    listenHasTouchChanges(callback);
    
    // callback is called when the result of "hasMouse()" changes.
    listenHasMouseChanges(callback);
    
    • Kevin Reid
      Kevin Reid about 12 years
    • davethegr8
      davethegr8 about 12 years
      I think you need to rethink your design if you want one app to be applicable to both desktop and mobile/touch but have different behaviors for each. I don't think what you're after is actually possible at this point, since a quick search on Google for "javascript detect mouse" shows one moderately useful post on quirksmode.org for detecting various states of the mouse (clicks, position, etc), but ZERO results on whether or not the mouse actually exists.
    • nraynaud
      nraynaud about 12 years
      Maybe that's because Google didn't help that I asked it here.
    • Parag Gajjar
      Parag Gajjar about 12 years
      Have you tried document mouseenter from jquery? $(document).mouseenter(function(e) { alert("mouse"); });
    • Abhishek
      Abhishek almost 11 years
      Late to the party, but I'll throw in here that you always have to keep in mind the many devices nowadays that support both touch AND mouse
    • nraynaud
      nraynaud almost 11 years
      my question was quite precisely worded about having no mouse, as in "no mouse at all". It was at the time to present the right interface to the right people by default. for an app that could be used on the field feet in the mud, in a moving vehicle or at the office on a PC.
    • Abhishek
      Abhishek almost 11 years
      Just a note for posterity - if you're looking to do the opposite (detect if mouse is available) stackoverflow.com/a/16423486/5056
    • Jordan Gray
      Jordan Gray over 10 years
      After considering nearly a dozen promising avenues only to reject each one within minutes, this question is driving me quite splendidly bonkers.
  • nraynaud
    nraynaud over 12 years
    I don't really know what to get from your post, but if ('onmouseover' in $('body')[0]) alert('onmouseover'); displays a message in iPhone too
  • Jon Gjengset
    Jon Gjengset about 12 years
    This only checks if the mouseover function is defined, which it would be on almost all browsers. It does not detect if a mouse is actually present.
  • Jon Gjengset
    Jon Gjengset about 12 years
    It's a good idea, but unfortunately the delay in response will make it unusable when the UI of the application depends on whether a mouse is available.. This is especially true if the application may be iframed, so mouse events will only hit it if the mouse moves over the iframe itself..
  • Dan
    Dan about 12 years
    Yep, I can't think of another way then, without doing browser sniffing or testing screen resolution and inferring the likely device capabilities.
  • nraynaud
    nraynaud about 12 years
    that's a creative idea! it's a bit more complicated than that because of synthesized mouse events in touch browser, but that's an idea!
  • Jon Gjengset
    Jon Gjengset about 12 years
    This won't work for devices that may or may not have touch screens and a mouse. For instance, a desktop Windows computer may be connected to a touch screen, but will usually also have a mouse, whereas a tablet may also be running Windows, but may not have a mouse connected..
  • Gigi
    Gigi about 12 years
    @Jonhoo Just assume that Desktop operating systems have a mouse attached. After all, they must support a wide range of software that was not developed with a touchscreen in mind.
  • Jon Gjengset
    Jon Gjengset about 12 years
    That is precisely what I'm trying to do. I'm trying to create an interface that will work in the best possible way on both tablets (no mouse) and with a mouse, but those interfaces are necessarily very different.
  • Jon Gjengset
    Jon Gjengset about 12 years
    What about tablets running plain Windows 8? Or Linux? Or laptops running Android?
  • Gigi
    Gigi about 12 years
    @Jonhoo Obviously this approach is less than optimal, but there is no portable way to know that (yet). If one is running a laptop with Android, just assume it is touch-capable. If one is running a Windows8 tablet, just assume it is mouse-capable (the OS must emulate the mouse for non-touch programs).
  • Mamey
    Mamey about 12 years
    This could work if the application starts with a splash screen and a "continue" button. If the mouse moves before the first mousedown event then you have a mouse. It would only fail if the button loaded directly under the mouse and the user has a very steady hand (even moving 1 pixel should be picked up).
  • Jon Gjengset
    Jon Gjengset about 12 years
    It's actually a pretty good suggestion, but it delays the time before the user gets to the real interface. Also, I'll have to provide a way of switching after the initial choice. Ends up being more work than if it could simply be detected..
  • Teemu Ikonen
    Teemu Ikonen about 12 years
    I agree with broady. You're best of by using a device detection (like DeviceAtlas) and select offered interface at load time.
  • Jon Gjengset
    Jon Gjengset about 12 years
    Because some browsers (IE9 for instance) report that the function exists even if it will never be triggered. I believe this is also the "correct" behavior.
  • Peter Burns
    Peter Burns over 11 years
    nice idea, but does not appear to work in our testing. iPads trigger this event.
  • Michael Haren
    Michael Haren about 11 years
    @JeffAtwood what did you end up doing in your case?
  • GameAlchemist
    GameAlchemist almost 11 years
    To know wether it is a mouse or a touch device you must also Hook the touch event and prevent its default handler. So the first Who Wins is the device you seek.
  • daredev
    daredev over 10 years
    @JeffAtwood @MichaelHaren this prevents the event from firing (see my answer): window.document.body.addEventListener('mousemove', function (e) { e.preventDefault(); e.stopImmediatePropagation(); }, true);
  • Samuel Rossille
    Samuel Rossille over 10 years
    Thanks for there good workaround suggestions... I think the main problem not being sovled I'll have to resort to one of these
  • T4NK3R
    T4NK3R over 10 years
    Asking the user is clearly the best way - if not always foolproof - And gives you a convenient place to put up upgrade-notifications and what not. I think you're over thinking the "problem"..
  • Chris Gunawardena
    Chris Gunawardena over 10 years
    Unfortunately mousemove gets triggered on click on a ipad. Only tested with simulator. For hasMouse() I was using if( !('ontouchstart' in window) ) return true; but doesn't work for touch supported laptops.
  • vsync
    vsync about 10 years
    Safari ipad returns true for 'onmousedown' in window
  • Peter Wooster
    Peter Wooster over 9 years
    iPads definitely trigger the mousemove event, right before the mousedown event. I've found that mousedown count > 0 and mousedown count == mousemove count to be a good way to detect no mouse. I can't duplicate this with a real mouse.
  • netzaffin
    netzaffin about 9 years
    Doesn't work for me, Ipad Safari (IOS8.3) also detects a mouse with this snippet
  • Hugo Silva
    Hugo Silva almost 9 years
    @netzaffin. Thanks for the feedback, I found it to be more consistent using mousedown instead of mouseover. Would you have a look at this fiddle from your IOS and let me know the outcome? Cheers jsfiddle.net/bkwb0qen/15/embedded/result
  • Jordan Gray
    Jordan Gray almost 9 years
    Specifically, any-pointer and any-hover will let you investigate all applicable device capabilities. Nice to get a peek at how we might solve this problem in the future! :)
  • suncat100
    suncat100 over 8 years
    Just detect a touchstart event to kill the mousemove event listener. That way, the mousemove eventlistener will never trigger on mobile devices.
  • 4esn0k
    4esn0k over 8 years
    window.matchMedia("(any-pointer: coarse)").matches === true ?
  • phreakhead
    phreakhead over 8 years
    Well, it works on Chrome 47 for OS X, at least. Reporting no ontouchstart.
  • Sebastien Lorber
    Sebastien Lorber almost 8 years
    great answer. Hopefully, the user always has a screen! I think it makes sense to build an interface where that ajust to current interaction mode of the user. On a touch laptop, it makes sense to adjust the app (ie the :hover elements and things like that) when user switch from mouse to touch. It seems unlikely that the user is currently using mouse + touch at the exact same time (I mean comoonn it's like having 2 mouses connected to the same computer hahaha)
  • 0xcaff
    0xcaff almost 8 years
    If you have a touchscreen with a mouse, only the input method used first will be detected.
  • 0xcaff
    0xcaff almost 8 years
    @suncat100 .@GameAlchemist your suggestions won't work on devices with both a touch screen and mouse.
  • suncat100
    suncat100 almost 8 years
    @GameAlchemist I said detect a touchstart event, not touch capability. If the visitor is on dual-input, if touch interaction occurs first, then it is incredibly likely that the visitor is going to continue this (even if a mouse is present). If mouse motion occurs before any touch event, it is incredibly likely visitor will continue using the mouse. The only way this could break, would be if the visitor is interchanging between touch screen and mouse to interact with the website, within the same browsing session. Thus, one should not only use this method progressively.
  • suncat100
    suncat100 almost 8 years
    Adding to the original topic question, you can add listeners for BOTH touch and mouse events (I mean listening for an actual event, not "touch" capability of the device). When they happen, you know they exist. It is not possible however as you note, to exclude that the visitor has mouse, just by detecting touch or even touch occurring. It is just very plausible by detecting actual usage of a specific device, that the visitor will continue using the same input method, on a dual-input device.
  • GameAlchemist
    GameAlchemist almost 8 years
    @suncat100 : you must be replying to caffinatedmonkey, since i think we say pretty much the same : hook both touch and mouse events, see which one triggers first, then stick to that one.
  • suncat100
    suncat100 almost 8 years
    @gameAlchemist : Right, sorry 'bout that! :D
  • Engineer
    Engineer over 5 years
    This is now so outdated that it is not relevant any longer.
  • MQuiggGeorgia
    MQuiggGeorgia over 4 years
    I personally like this answer but as of now (10/19), @media hover and pointer CSS queries are only available on ~85% of devices worldwide according to caniuse.com. Certainly not bad, 95% or above is preferable. Hopefully this will become standard on devices soon.
  • Blackbam
    Blackbam over 4 years
    @MQuiggGeorgia Basically I agree with your criticism basically, it is not supported everywhere yet. Still caniuse.com for me says it is supported 91.2% (caniuse.com/#feat=css-media-interaction). Having a closer look it is supported everywhere except for IE 11 and special flattened browsers on mobile. To be fair this is true for any modern feature, as Microsoft stopped implementing IE features long ago. For IE 11 you might use a fallback from the other answers here.
  • ashleedawg
    ashleedawg over 4 years
    @SebastienLorber - hate to break it to you but users don't necessarily always have a screen. (Is it possible to use javascript to detect if a screen reader is running on a users machine?)
  • FrancescoMM
    FrancescoMM over 4 years
    iPad, iPhone, Android triggere the musemouve on a touch event but you can avoid this behaviour by listening to the touch events and sending a event.preventDefault(). That shpuld stop the mousemove event to be triggered on touch, so if it gets triggered it is a real mouse event
  • raquelhortab
    raquelhortab over 3 years
    September 2020: I'm trying the media match (hover: hover) in an android smartphone and it matches while in the w3 link says it shouldn't
  • Blackbam
    Blackbam over 3 years
    @raquelhortab If the W3 says it shouldn't than it shouldn't. Which browser do you use?
  • raquelhortab
    raquelhortab over 3 years
    @Blackbam Chrome 85.0.4183.101 on Android 8.1.0 Aquaris V Build. I found a workaround to my problem anyway so I didn't look much into it
  • vsync
    vsync about 3 years
    You can have both mouse and touchscreen (some windows laptops have screen touch capabilities for years...)
  • Cluster
    Cluster almost 3 years
    window.matchMedia("(any-pointer: fine)").matches returns true on all my mobile browsers and desktop for some reason. window.matchMedia("(any-hover: hover)").matches always returns true too, even on mobile devices without a mouse. Only window.matchMedia("(any-pointer: coarse)").matches returns true on mobile devices and false on desktop but it doesn't take into account connected mouse or s-pen.
  • Cluster
    Cluster almost 3 years
    window.matchMedia("(any-pointer: coarse)").matches returns true on mobile devices and false on desktop but it doesn't take into account connected mouse or s-pen.
  • Blackbam
    Blackbam almost 3 years
    @Cluster This article describes the features in detail from a practical point of view: css-tricks.com/touch-devices-not-judged-size
  • Robin93K
    Robin93K about 2 years
    window.matchMedia("(any-hover: none)").matches on any chromium browser on desktop always returns false, while window.matchMedia("(any-pointer: fine)").matches on any chromium browser on touch laptops returns false, so despite caniuse saying this works, it absolutely doesn't return the correct values in chromium browsers!
  • Blackbam
    Blackbam about 2 years
    @Robin93K What I would recommend you is the following article from CSS tricks: css-tricks.com/…
  • Robin93K
    Robin93K about 2 years
    @Blackbam That article is nice, but it also fails to mention this solution still won't work in chromium browsers even if you do understand how to use it, as the media queries in chromium on windows simply do not work correctly. Simply execute window.matchMedia("(any-hover: none)").matches in the developer tools on any page in the chromium browser of your choice (chrome, vivaldi, iron, edge). Firefox is the only browser that fully supports "CSS4 media interaction".
  • Blackbam
    Blackbam about 2 years
    @Robin93K If not yet everything works as intended in chromium-based browsers they will hopefully implement it. It is still a candidate recommendation draft, not yet a standard (w3.org/TR/mediaqueries-4/#mf-interaction). Nevertheless it is the best solution I have encountered for this purpose so far. In my use cases it all worked, but I did not yet use the any- variants in practice yet.