24

I currently use the following test (taken out of Modernizr) to detect touch support:

function is_touch_device() {
    var bool;
    if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
        bool = true;
    } else {
        injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function(node) {
            bool = node.offsetTop === 9;
        });
    }
    return bool;
}

But some devices are both touch and mouse driven, so I want a seperate function to detect if a device has mouse support. What's a good way to do this check?

Ultimately my intention is to be able to do these:

if(is_touch_device())

if(has_mouse_support())

if(is_touch_device() && has_mouse_support())
  • While it does not answer your question explicitly, I found this article interesting wrt touch vs. mouse support: http://www.html5rocks.com/en/mobile/touchandmouse/ – John Koerner Jan 10 '14 at 20:53
  • 2
    Untested (and works only if the mouse actually is moved): var mouse= false; window.onmousemove = function(){mouse= true} – Reeno Jan 10 '14 at 20:59
  • @Reeno Clever, I think that should work but will wait for a more tested answer in case someone has a known solution. Edit: that's true it will give false positive if mouse is never moved hmmm. –  Jan 10 '14 at 21:01
  • 2
    if someone never wiggles the mouse, they probably prefer other modes of interaction anyway... – dandavis Jan 10 '14 at 21:09
  • Mhm. According to http://www.html5rocks.com/en/mobile/touchandmouse/#toc-1 my solution doesn't work. Damn :) Here's a long discussion about the topic: https://github.com/Modernizr/Modernizr/issues/869 Seems like there isn't a good solution – Reeno Jan 10 '14 at 21:09
  • You can test for touchscreen support http://stackoverflow.com/questions/11387805/touchscreen-media-queries – Wayne Jan 10 '14 at 21:12
  • @Reeno: how about another event like yours, but setting touch=true ontouchstart, and then mouse gets set to !touch. since your link claims touchstart fires first, it should work. you can also measure the time between mousedown and mouseup for absolute proof. also, watch out for window8 touch laptops which have both... – dandavis Jan 10 '14 at 21:12

7 Answers7

37

There's a CSS media just for that!

You can check whether some device has a mouse by getting the value of the pointer CSS media feature:

if (matchMedia('(pointer:fine)').matches) {
  // Device has a mouse
}

Because it's CSS you don't even need to use JavaScript:

@media (pointer: fine) {
  /* Rules for devices with mouse here */
}
josemmo
  • 6,523
  • 3
  • 35
  • 49
  • 1
    So awesome, I thought feature like this would be great, but never hoped it would actually exist! – Tomáš Zato Dec 25 '18 at 11:46
  • 1
    Not working for me on Android Phone with Firefox. `(pointer: fine)` is a match despite having no pointing device. – user2444499 Sep 30 '19 at 03:35
  • 1
    Your test is for primary device, only. It doesn't work if mouse is an additional device to some built-in one, such as touch input. You could use `any-pointer` for that, but Chrome fails to support this in a selected amount of cases like on a convertible with Win10 such as Surface devices with externally attached mouse. Due to its market share and the obvious lack of interest in this [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=1088262) on behalf of Chrome devs, this feature is totally useless. Thanks, Google, for turning Chrome into the next MSIE-like PITA corrupting the web. – Thomas Urban Aug 03 '21 at 20:57
8

I am currently using the following (jQuery) and I haven't found any flaws yet on specific devices

$(window).bind('mousemove.hasMouse',function(){
    $(window).unbind('.hasMouse');
    agent.hasMouse=true;
}).bind('touchstart.hasMouse',function(){
    $(window).unbind('.hasMouse');
    agent.hasMouse=false;
});

Explanation: Mouse devices (also touch screen laptops) first fire mousemove before they can fire touchstart and hasMouse is set to TRUE. Touch devices (also for instance iOS which fires mousemove) FIRST fire touchstart upon click, and then mousemove. Then is why hasMouse will be set to FALSE.

The only catch is that this depends on user interaction, the value will only be correct after mouse move or touchstart so cannot be trusted to use on page load.


EDIT: This answer is now outdated. There's better modern solutions. Also I don't advice using jQuery anymore nowadays.

Hacktisch
  • 1,392
  • 15
  • 33
2

As mentioned in the question comments, specifically on https://github.com/Modernizr/Modernizr/issues/869, there is no good answer yet.

drzaus
  • 24,171
  • 16
  • 142
  • 201
  • 1
    I really only put this as an answer in order to 'officially' mark [this other question](http://stackoverflow.com/questions/21887887/javascript-detect-desktop-touch-and-mouse-support) as a duplicate – drzaus Jun 13 '14 at 16:22
  • What about checking for the presence of ´window.MouseEvent´ as [this comment](https://github.com/Modernizr/Modernizr/issues/869#issuecomment-15090687) suggests? – Design by Adrian Dec 01 '15 at 10:16
  • @DesignbyAdrian paraphrasing from [a couple comments below the one you linked](https://github.com/Modernizr/Modernizr/issues/869#issuecomment-15094412): inconsistent behavior with tablets, particularly iPad and Surface – drzaus Dec 01 '15 at 21:44
2

Answer by @josemmo is not working for me: on android phone with mouse attached matchMedia('(pointer:fine)').matches does not match.

Fortunately, I've succeeded with another media query: hover.

if (matchMedia('(hover:hover)').matches) {
  // Device has a mouse
}
danronmoon
  • 3,814
  • 5
  • 34
  • 56
johndoe
  • 103
  • 7
  • Far from perfect. My mobile phone (Galaxy S8+ ) has a mouse according to the code directly above. Yes, I was surprised but the fact remains. – Martyn Wynn Feb 28 '20 at 18:53
  • 1
    May be that is because of Samsung S Pen support – johndoe Mar 01 '20 at 12:49
  • A lot of smartphones do support hover, even if no mouse is attached, so it's not a good solution. I've tried this sample code on a bare OnePlus 3 and it returned true, while I don't have a mouse. – Quentin D Jan 16 '21 at 13:18
0
var clickHandler = (isMouseEventSupported('click') ? 'click' : 'touchstart');

function isMouseEventSupported(eventName) {
    var element = document.createElement('div');
    eventName = 'on' + eventName;
    var isSupported = (eventName in element);
    if (!isSupported) {
       element.setAttribute(eventName, 'return;');
       isSupported = typeof element[eventName] == 'function';
    }
    element = null;
    return isSupported;
}

This is code from a friend/coworker of mine and he based it off of: http://perfectionkills.com/detecting-event-support-without-browser-sniffing/

  • 1
    I'm pretty sure all touch devices support onclick events... That doesn't mean they have a mouse. – Rudie Aug 26 '16 at 10:42
-1

As of 2021 pointerevents is implemented in all major browsers.

It gives you the posibility to dynamically detect pointerdevices mouse, touch and pen.

var is_touch_device=(('ontouchstart' in window)||
                    (navigator.maxTouchPoints > 0)||
                    (navigator.msMaxTouchPoints > 0));

var has_mouse_support=false;
document.addEventListener("pointermove", function(evt) {
  var pointerType=evt.pointerType;
  /*** Safari quirk  ***/
  if(pointerType==="touch"&&evt.height===117.97119140625
                         &&evt.height===evt.width)pointerType="mouse";
  /*** Safari quirk  ***/
  has_mouse_support=(pointerType==="mouse");
}

It is of course dependent on the user moving the mousepointer.

Even safari on ipadOS 14.4.2 detects it, if AssistiveTouch is activated! But there seems to be some quirks in pointerType detection there. It detects pointerType as mouse first time the mouse is used and no touch has been performed. But if you later use touch, it will not detect and change to pointerType of mouse, if mouse is used after touch! No surprise!

Edit: After some messing around with ipadOS safari i have discovered that, when mouse is used after touch, the pointerevent width and height are the exact same, which in ipadOS 14.4.2 is 117.97119140625 every time mouse is used. This can be used as a not to reliable workaround. Who knows when they will change the width/height? Another peculiarity with pointermove detection in ipadOS, is that mouse move is only detected on buttom press on mouse.

It is not tested with pen on ipad/iphone. Who knows which quirks this will show?

gerteb
  • 121
  • 1
  • 12
-2

There is no immediate way of knowing, you'll have to wait for a touch event or a mouse event.

Presuming you want to detect either mouse or touch you can do the following: listen for touchstart and mousemove (the latter can fire on touch devices without an actual mouse). Whichever one fires first is 99% bound to be what you're looking for.

This does not take in account devices that actually have both.

document.addEventListener('mousemove', onMouseMove, true)
document.addEventListener('touchstart', onTouchStart, true)
function onTouchStart(){
  removeListeners()
  // touch detected: do stuff
}
function onMouseMove(){
  removeListeners()
  // mouse detected: do stuff
}
function removeListeners(){
  document.removeEventListener('mousemove', onMouseMove, true)
  document.removeEventListener('touchstart', onTouchStart, true)
}
Sjeiti
  • 2,468
  • 1
  • 31
  • 33
  • This does not work because browsers seem to emulate mouse events when the user touches the screen => `mousemove` is fired when the user touches the screen. – AlexandreS Jul 13 '23 at 15:14
  • 1
    @AlexandreS Did you test this, or did you assume this does not work? Because when I run this on a touch device (not an emulated one), the touch events are detected first, which removes both event listeners. So mouse events are never detected on a touch device (they are if you disable the `removeListeners` method). Tested on mobile Firefox and Chrome. – Sjeiti Jul 26 '23 at 14:16
  • Oh yeah you're right, I downvoted your answer too quickly, my bad. I implemented it but I did not use the fact that the devices fires an event before the other one – AlexandreS Jul 27 '23 at 06:58