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

23

180

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);
Tavia answered 20/10, 2011 at 15:53 Comment(8)
Related question: Detect whether browser has keyboard/arrow keys in web pageMiddaugh
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.Orme
Maybe that's because Google didn't help that I asked it here.Tavia
Have you tried document mouseenter from jquery? $(document).mouseenter(function(e) { alert("mouse"); });Solidarity
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 mouseDecarlo
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.Tavia
Just a note for posterity - if you're looking to do the opposite (detect if mouse is available) https://mcmap.net/q/42260/-reliably-detect-if-a-user-is-using-a-mouse-on-a-webpageDecarlo
After considering nearly a dozen promising avenues only to reject each one within minutes, this question is driving me quite splendidly bonkers.Fabled
I
105

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

Intoxicate answered 17/10, 2018 at 12:10 Comment(12)
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.Eckmann
@Eckmann 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.Intoxicate
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'tSalon
@Salon If the W3 says it shouldn't than it shouldn't. Which browser do you use?Intoxicate
@Intoxicate 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 itSalon
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.Woolley
@Woolley This article describes the features in detail from a practical point of view: css-tricks.com/touch-devices-not-judged-sizeIntoxicate
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!Decreasing
@Decreasing What I would recommend you is the following article from CSS tricks: css-tricks.com/…Intoxicate
@Intoxicate 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".Decreasing
@Decreasing 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.Intoxicate
@matteo Did you add a .matches after the bracket? It could be a bug or bad implementation otherwise but I doubt it ;)Intoxicate
I
77

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.

Ingunna answered 1/8, 2013 at 17:58 Comment(2)
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)Ravo
@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?)Radiotelephone
F
63

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

Faulty answered 8/2, 2012 at 20:0 Comment(16)
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..Headreach
Yep, I can't think of another way then, without doing browser sniffing or testing screen resolution and inferring the likely device capabilities.Faulty
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!Tavia
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).Beth
nice idea, but does not appear to work in our testing. iPads trigger this event.Avatar
@JeffAtwood what did you end up doing in your case?Abana
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.Widgeon
@JeffAtwood @MichaelHaren this prevents the event from firing (see my answer): window.document.body.addEventListener('mousemove', function (e) { e.preventDefault(); e.stopImmediatePropagation(); }, true);Poppycock
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.Treponema
Just detect a touchstart event to kill the mousemove event listener. That way, the mousemove eventlistener will never trigger on mobile devices.Botryoidal
@Botryoidal .@Widgeon your suggestions won't work on devices with both a touch screen and mouse.Crassulaceous
@Widgeon 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.Botryoidal
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.Botryoidal
@Botryoidal : 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.Widgeon
@gameAlchemist : Right, sorry 'bout that! :DBotryoidal
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 eventLeper
H
24

@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

Henrietta answered 27/2, 2014 at 3:41 Comment(3)
Doesn't work for me, Ipad Safari (IOS8.3) also detects a mouse with this snippetTanked
@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/resultHenrietta
If you have a touchscreen with a mouse, only the input method used first will be detected.Crassulaceous
M
13

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

Monometallism answered 2/8, 2013 at 22:54 Comment(0)
A
8

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.

Adipocere answered 16/5, 2015 at 16:35 Comment(2)
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! :)Fabled
window.matchMedia("(any-pointer: coarse)").matches === true ?Blende
K
7

Since you're planning to offer a way to switch between the interfaces anyway, would it be feasible to simply ask the user to click a link or a button to "enter" the correct version of the application? Then you could remember their preference for future visits. It's not high-tech, but it's 100% reliable :-)

Kling answered 14/2, 2012 at 0:41 Comment(2)
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..Headreach
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"..Grot
A
6

This worked for me in a similar situation. Basically, assume the user doesn't have a mouse until you see a short series of consecutive mousemoves, without intervening mousedowns or mouseups. Not very elegant, but it works.

var mousedown = false;
var mousemovecount = 0;
function onMouseDown(e){
    mousemovecount = 0;
    mousedown = true;
}
function onMouseUp(e){
    mousedown = false;
    mousemovecount = 0;
}
function onMouseMove(e) {
    if(!mousedown) {
        mousemovecount++;
        if(mousemovecount > 5){
            window.removeEventListener('mousemove', onMouseMove, false);
            console.log("mouse moved");
            $('body').addClass('has-mouse');
        }
    } else {
        mousemovecount = 0;
    }
}
window.addEventListener('mousemove', onMouseMove, false);
window.addEventListener('mousedown', onMouseDown, false);
window.addEventListener('mouseup', onMouseUp, false);
Amero answered 19/5, 2016 at 15:14 Comment(0)
P
4

@SamuelRossille No browsers that I'm aware of expose the existence of (or lack thereof) a mouse, unfortunately.

So, with that being said, we just have to try and do the best we can with our existing option... events. I know it's not exactly what you're looking for... agreed it is currently far from ideal.

We can do our best to figure out whether a user is using a mouse or touch at any given moment. Here is a quick and dirty example using jQuery & Knockout:

//namespace
window.ns = {};

// for starters, we'll briefly assume if touch exists, they are using it - default behavior
ns.usingTouch = ko.observable(Modernizr.touch); //using Modernizr here for brevity.  Substitute any touch detection method you desire

// now, let's sort out the mouse
ns.usingMouse = ko.computed(function () {
    //touch
    if (ns.usingTouch()) {
        //first, kill the base mousemove event
        //I really wish browsers would stop trying to handle this within touch events in the first place
        window.document.body.addEventListener('mousemove', function (e) {
            e.preventDefault();
            e.stopImmediatePropagation();
        }, true);

        //remove mouse class from body
        $('body').removeClass("mouse");

        //switch off touch listener
        $(document).off(".ns-touch");

        // switch on a mouse listener
        $(document).on('mousemove.ns-mouse', function (e) {
            if (Math.abs(window.lastX - e.clientX) > 0 || window.lastY !== e.clientY) {
                ns.usingTouch(false);  //this will trigger re-evaluation of ns.usingMouse() and result in ns.usingMouse() === true
            }
        });

        return false;
    }
    //mouse
    else {
        //add mouse class to body for styling
        $('body').addClass("mouse");

        //switch off mouse listener
        $(document).off(".ns-mouse");

        //switch on a touch listener
        $(document).on('touchstart.ns-touch', function () { ns.usingTouch(true) });

        return true;
    }
});

//tests:
//ns.usingMouse()
//$('body').hasClass('mouse');

You can now bind/subscribe to usingMouse() & usingTouch() and/or style your interface with the body.mouse class. The interface will switch back and forth as soon as a mouse cursor is detected and on touchstart.

Hopefully we'll have some better options from the browser vendors soon.

Poppycock answered 20/10, 2011 at 15:53 Comment(0)
P
2

Tera-WURFL can tell you the capabilities of the device that is visiting your site by comparing the browser signature against its database. Give it a look, its free!

Peart answered 10/2, 2012 at 4:16 Comment(5)
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..Headreach
@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.Peart
What about tablets running plain Windows 8? Or Linux? Or laptops running Android?Headreach
@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).Peart
This is now so outdated that it is not relevant any longer.Sexton
S
2

Why don't you just detect if it has the ability to sense touches and/or to react to mouse movements?

// This will also return false on
// touch-enabled browsers like Chrome
function has_touch() {
  return !!('ontouchstart' in window);
}

function has_mouse() {
  return !!('onmousemove' in window);
}
Scrope answered 15/2, 2012 at 16:13 Comment(2)
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.Headreach
Well, it works on Chrome 47 for OS X, at least. Reporting no ontouchstart.Salzburg
Y
1

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.

Yee answered 5/7, 2018 at 10:22 Comment(0)
T
0

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.

Trusty answered 15/2, 2012 at 10:22 Comment(0)
L
0

As others have pointed out, definitively detecting whether or not they have a mouse is unreliable. This can easily change, depending on the device. It's definitely something you cannot do reliably with a boolean true or false, at least on a document scale.

Touch events and mouse events are exclusive. So this can help somewhat on taking different actions. The problem is the touch events are closer to the mouse up/down/move events, and also trigger a click event.

From you question you say you want to have a hover to preview. Beyond that I don't know any other specifics about your interface. I'm assuming that with the lack of a mouse you want a tap to preview, while a click does a different action because of the hover preview.

If that is the case you can take somewhat of a lazy approach to detection:

An onclick event will always be preceded by an onmouseover event with a mouse. So make a note that the mouse is on top of the element that has been clicked.

You could do this with a document-wide onmousemove event. You can use event.target to record which element the mouse is residing on. Then inside your onclick events you can check to see whether or not the mouse is actually over the element being clicked (or a child of the element).

From there you can choose either to rely on the click event for both and take an A or B action depending on the result. The B action could be nothing if some touch devices don't emit a click event (instead you would have to rely on ontouch* events).

Lamprophyre answered 2/8, 2013 at 16:56 Comment(0)
O
0

I don't think it's possible to identify touch-only device (to my knowledge of course). The main issue is all mouse and keyboard events are fired by touch devices too. See the following example, both alerts return true for touch devices.

function is_touch_present() {
  return ('ontouchstart' in window) || ('onmsgesturechange' in window);
}

function is_mouse_present() {
  return (('onmousedown' in window) && ('onmouseup' in window) && ('onmousemove' in window) && ('onclick' in window) && ('ondblclick' in window) && ('onmousemove' in window) && ('onmouseover' in window) && ('onmouseout' in window) && ('oncontextmenu' in window));
}

alert("Touch Present: " + is_touch_present());
alert("Mouse Present: " + is_mouse_present());
Our answered 2/8, 2013 at 17:45 Comment(1)
Safari ipad returns true for 'onmousedown' in windowHymnology
E
0

The best idea in my opinion is the mousemove listener (currently the top answer). I believe that this method needs to be tweaked a bit. It is true that touch-based browsers emulate even the mousemove event, as you can see in this iOS discussion, so we should be a little careful.

It makes sense that touch-based browsers will only emulate this event when the user taps the screen (the user's finger is down). This means we should add a test during our mousemove handler to see which mouse button is down (if any) during the event. If no mouse button is down, we can safely assume a real mouse is present. If a mouse button is down, the test remains inconclusive.

So how would this be implemented? This question shows that the most reliable method to examine which mouse button is down during a mousemove is to actually listen for 3 events in document level: mousemove, mousedown and mouseup. The up and down will only set a global boolean flag. The move will perform the test. If you have a move and the boolean is false, we can assume a mouse is present. See question for exact code examples.

One final comment.. This test isn't ideal because it can't be performed in load time. Therefore, I would use a progressive enhancement method as previously suggested. By default show a version which does not support the mouse-specific hover interface. If a mouse is discovered, enable this mode in runtime using JS. This should appear as seamless as possible to the user.

In order to support changes in the user's configuration (ie mouse has been disconnected), you can periodically re-test. Although I believe it will be better in this case to simply notify the user about the 2 modes and let users manually switch between them (much like the mobile/desktop choice which can always be reversed).

Empoison answered 2/8, 2013 at 22:5 Comment(2)
Thanks for there good workaround suggestions... I think the main problem not being sovled I'll have to resort to one of thesePent
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.Goodtempered
H
0

Ran some tests on various PC's, Linux's, iPhone, Android phones and tabs. Weird that there is no easy bullet-proof solution. Problem arises when some that have Touch and no mouse still present Touch and Mouse events to application. Since do want to support mouse-only and touch-only instances, want to process both, but this causes double occurrences of user interactions. If can know mouse is not present on device, then can know to ignore fake/inserted mouse events. Tried setting flag if MouseMove is encountered, but some browsers throw fake MouseMove as well as MouseUp and MouseDown. Tried examining timestamps but figured this was too risky. Bottom line: I found the browsers that created the fake mouse events always inserted a single MouseMove just prior to the inserted MouseDown. In 99.99% of my cases, when running on a system that has a real mouse, there are multiple consecutive MouseMove events - at least two. So, keep track of whether system encounters two consecutive MouseMove events and declare there is no mouse present if this condition is never met. This is probably too simple, but its working on all my test setups. Think I'll stick with it until I find a better solution. - Jim W

Haulm answered 18/8, 2013 at 3:42 Comment(0)
B
0

A simple solution in jQuery to detect mouse usage, which solves the issue where mobile devices also trigger 'mousemove' event. Simply add a touchstart listener to remove the mousemove listener, so that it doesn't get triggered on touch.

$('body').one('touchstart.test', function(e) {
  // Remove the mousemove listener for touch devices
  $('body').off('mousemove.test');
}).one('mousemove.test', function(e) {
  // MOUSE!
});

Of course, the device could still be touch AND mouse, but the above will guarantee that a real mouse was used.

Botryoidal answered 3/8, 2015 at 19:13 Comment(0)
E
0

Just found a solution that I think is quite elegant.

// flag as mouse interaction by default
var isMouse = true;

// detect a touch event start and flag it
$(window).on('touchstart', function () {
  // if triggers touchstart, toggle flag
  // since touch events come before mouse event / click
  // callback of mouse event will get in callback
  // `isMouse === false`
  isMouse = false;
});

// now the code that you want to write
// should also work with `mouse*` events
$('a.someClass').on('click', function () {
  if (isMouse) {
    // interaction with mouse event
    // ...
  } else {
    // reset for the next event detection
    // this is crucial for devices that have both
    // touch screen and mouse
    isMouse = true;

    // interaction with touch event
    // ...
  }
});
Eupheemia answered 3/2, 2017 at 8:43 Comment(0)
P
0

I spent hours figuring out this problem for my Phonegap app and I came up with this hack. It generates a console warning if the event triggered is a "passive" event, meaning it doesn't actuate any change, but it works! I would be interested in any ideas to improve or a better method. But this effectively allows my to use $.touch() universally.

$(document).ready(function(){
  $("#aButton").touch(function(origElement, origEvent){
    console.log('Original target ID: ' + $(origEvent.target).attr('id'));
  });
});

$.fn.touch = function (callback) {
    var touch = false;
    $(this).on("click", function(e){
        if (!touch)
        {
            console.log("I click!");
            let callbackReal = callback.bind(this);
            callbackReal(this, e);
        }else{
            touch = true;
        }
        touch = false;
    });
    $(this).on("touchstart", function(e){
        if (typeof e.touches != typeof undefined)
        {
            e.preventDefault();
            touch = true;
            console.log("I touch!");
            let callbackReal = callback.bind(this);
            callbackReal(this, e);
        }
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button id="aButton">A Button</button>
Potassium answered 27/2, 2019 at 4:23 Comment(0)
R
0

Run the snippet (below) to try this out:

var msg = (window.matchMedia("(any-pointer: coarse)").matches ? "Touchscreen" : "Mouse");

var msg = (window.matchMedia("(any-pointer: coarse)").matches ? "Touchscreen" : "Mouse");
document.getElementById('info').innerHTML = msg;
body {
  background: black;
  color: cornflowerblue;
}

#info {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 30vmin;
  font-family: verdana, arial, helvetica;
}
<div id='info'></div>

(Here's more info about window.matchMedia.)

Radiotelephone answered 28/11, 2020 at 12:12 Comment(2)
You can have both mouse and touchscreen (some windows laptops have screen touch capabilities for years...)Hymnology
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.Woolley
R
-4

I'd strongly recommend against this approach. Consider touch-screen, desktop-sized devices, and you have a different set of problems to solve.

Please make your app usable without a mouse (i.e. no hover preview).

Reaves answered 12/2, 2012 at 14:26 Comment(2)
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.Headreach
I agree with broady. You're best of by using a device detection (like DeviceAtlas) and select offered interface at load time.Amalia
W
-7

It is generally a better idea to detect if the mouseover function is supported rather than detecting the OS/browser type. You can do that simply by the following:

if (element.mouseover) {
    //Supports the mouseover event
}

Be sure that you don't do the following:

if (element.mouseover()) {
    // Supports the mouseover event
}

The latter would actually call the method, rather than check for its existence.

You can read up more here: http://www.quirksmode.org/js/support.html

Wicopy answered 20/10, 2011 at 15:59 Comment(2)
I don't really know what to get from your post, but if ('onmouseover' in $('body')[0]) alert('onmouseover'); displays a message in iPhone tooTavia
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.Headreach

© 2022 - 2024 — McMap. All rights reserved.