Mozilla

Detecting touch: it’s the ‘why’, not the ‘how’

One common aspect of making a website or application “mobile friendly” is the inclusion of tweaks, additional functionality or interface elements that are particularly aimed at touchscreens. A very common question from developers is now “How can I detect a touch-capable device?”

Feature detection for touch

Although there used to be a few incompatibilities and proprietary solutions in the past (such as Mozilla’s experimental, vendor-prefixed event model), almost all browsers now implement the same Touch Events model (based on a solution first introduced by Apple for iOS Safari, which subsequently was adopted by other browsers and retrospectively turned into a W3C draft specification).

As a result, being able to programmatically detect whether or not a particular browser supports touch interactions involves a very simple feature detection:

if ('ontouchstart' in window) {
  /* browser with Touch Events
     running on touch-capable device */
}

This snippet works reliably in modern browser, but older versions notoriously had a few quirks and inconsistencies which required jumping through various different detection strategy hoops. If your application is targetting these older browsers, I’d recommend having a look at Modernizr – and in particular its various touch test approaches – which smooths over most of these issues.

I noted above that “almost all browsers” support this touch event model. The big exception here is Internet Explorer. While up to IE9 there was no support for any low-level touch interaction, IE10 introduced support for Microsoft’s own Pointer Events. This event model – which has since been submitted for W3C standardisation – unifies “pointer” devices (mouse, stylus, touch, etc) under a single new class of events. As this model does not, by design, include any separate ‘touch’, the feature detection for ontouchstart will naturally not work. The suggested method of detecting if a browser using Pointer Events is running on a touch-enabled device instead involves checking for the existence and return value of navigator.maxTouchPoints (note that Microsoft’s Pointer Events are currently still vendor-prefixed, so in practice we’ll be looking for navigator.msMaxTouchPoints). If the property exists and returns a value greater than 0, we have touch support.

if (navigator.msMaxTouchPoints > 0) {
  /* IE with pointer events running
     on touch-capable device */
}

Adding this to our previous feature detect – and also including the non-vendor-prefixed version of the Pointer Events one for future compatibility – we get a still reasonably compact code snippet:

if (('ontouchstart' in window) ||
     (navigator.maxTouchPoints > 0) ||
     (navigator.msMaxTouchPoints > 0)) {
      /* browser with either Touch Events of Pointer Events
         running on touch-capable device */
}

How touch detection is used

Now, there are already quite a few commonly-used techniques for “touch optimisation” which take advantage of these sorts of feature detects. The most common use cases for detecting touch is to increase the responsiveness of an interface for touch users.

When using a touchscreen interface, browsers introduce an artificial delay (in the range of about 300ms) between a touch action – such as tapping a link or a button – and the time the actual click event is being fired.

More specifically, in browsers that support Touch Events the delay happens between touchend and the simulated mouse events that these browser also fire for compatibility with mouse-centric scripts:

touchstart > [touchmove]+ > touchend > delay > mousemove > mousedown > mouseup > click

See the event listener test page to see the order in which events are being fired, code available on GitHub.

This delay has been introduced to allow users to double-tap (for instance, to zoom in/out of a page) without accidentally activating any page elements.

It’s interesting to note that Firefox and Chrome on Android have removed this delay for pages with a fixed, non-zoomable viewport.

<meta name="viewport" value="... user-scalable = no ...">

See the event listener with user-scalable=no test page, code available on GitHub.

There is some discussion of tweaking Chrome’s behavior further for other situations – see issue 169642 in the Chromium bug tracker.

Although this affordance is clearly necessary, it can make a web app feel slightly laggy and unresponsive. One common trick has been to check for touch support and, if present, react directly to a touch event (either touchstart – as soon as the user touches the screen – or touchend – after the user has lifted their finger) instead of the traditional click:

/* if touch supported, listen to 'touchend', otherwise 'click' */
var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click');
blah.addEventListener(clickEvent, function() { ... });

Although this type of optimisation is now widely used, it is based on a logical fallacy which is now starting to become more apparent.

The artificial delay is also present in browsers that use Pointer Events.

pointerover > mouseover > pointerdown > mousedown > pointermove > mousemove > pointerup > mouseup > pointerout > mouseout > delay > click

Although it’s possible to extend the above optimisation approach to check navigator.maxTouchPoints and to then hook up our listener to pointerup rather than click, there is a much simpler way: setting the touch-action CSS property of our element to none eliminates the delay.

/* suppress default touch action like double-tap zoom */
a, button {
  -ms-touch-action: none;
      touch-action: none;
}

See the event listener with touch-action:none test page, code available on GitHub.

False assumptions

It’s important to note that these types of optimisations based on the availability of touch have a fundamental flaw: they make assumptions about user behavior based on device capabilities. More explicitly, the example above assumes that because a device is capable of touch input, a user will in fact use touch as the only way to interact with it.

This assumption probably held some truth a few years back, when the only devices that featured touch input were the classic “mobile” and “tablet”. Here, touchscreens were the only input method available. In recent months, though, we’ve seen a whole new class of devices which feature both a traditional laptop/desktop form factor (including a mouse, trackpad, keyboard) and a touchscreen, such as the various Windows 8 machines or Google’s Chromebook Pixel.

As an aside, even in the case of mobile phones or tablets, it was already possible – on some platforms – for users to add further input devices. While iOS only caters for pairing an additional bluetooth keyboard to an iPhone/iPad purely for text input, Android and Blackberry OS also let users add a mouse.

On Android, this mouse will act exactly like a “touch”, even firing the same sequence of touch events and simulated mouse events, including the dreaded delay in between – so optimisations like our example above will still work fine. Blackberry OS, however, purely fires mouse events, leading to the same sort of problem outlined below.

The implications of this change are slowly beginning to dawn on developers: that touch support does not necessarily mean “mobile” anymore, and more importantly that even if touch is available, it may not be the primary or exclusive input method that a user chooses. In fact, a user may even transition between any of their available input methods in the course of their interaction.

The innocent code snippets above can have quite annoying consequences on this new class of devices. In browsers that use Touch Events:

var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click');

is basically saying “if the device support touch, only listen to touchend and not click” – which, on a multi-input device, immediately shuts out any interaction via mouse, trackpad or keyboard.

Touch or mouse?

So what’s the solution to this new conundrum of touch-capable devices that may also have other input methods? While some developers have started to look at complementing a touch feature detection with additional user agent sniffing, I believe that the answer – as in so many other cases in web development – is to accept that we can’t fully detect or control how our users will interact with our web sites and applications, and to be input-agnostic. Instead of making assumptions, our code should cater for all eventualities. Specifically, instead of making the decision about whether to react to click or touchend/touchstart mutually exclusive, these should all be taken into consideration as complementary.

Certainly, this may involve a bit more code, but the end result will be that our application will work for the largest number of users. One approach, already familiar to developers who’ve strived to make their mouse-specific interfaces also work for keyboard users, would be to simply “double up” your event listeners (while taking care to prevent the functionality from firing twice by stopping the simulated mouse events that are fired following the touch events):

blah.addEventListener('touchend', function(e) {
  /* prevent delay and simulated mouse events */
  e.preventDefault();
  someFunction()
});
blah.addEventListener('click', someFunction);

If this isn’t DRY enough for you, there are of course fancier approaches, such as only defining your functions for click and then bypassing the dreaded delay by explicitly firing that handler:

blah.addEventListener('touchend', function(e) {
  /* prevent delay and simulated mouse events */
  e.preventDefault();
  /* trigger the actual behavior we bound to the 'click' event */
  e.target.click();
})
blah.addEventListener('click', function() {
  /* actual functionality */
});

That last snippet does not cover all possible scenarios though. For a more robust implementation of the same principle, see the FastClick script from FT labs.

Being input-agnostic

Of course, battling with delay on touch devices is not the only reason why developers want to check for touch capabilities. Current discussions – such as this issue in Modernizr about detecting a mouse user – now revolve around offering completely different interfaces to touch users, compared to mouse or keyboard, and whether or not a particular browser/device supports things like hovering. And even beyond JavaScript, similar concepts (pointer and hover media features) are being proposed for Media Queries Level 4. But the principle is still the same: as there are now common multi-input devices, it’s not straightforward (and in many cases, impossible) anymore to determine if a user is on a device that exclusively supports touch.

The more generic approach taken in Microsoft’s Pointer Events specification – which is already being scheduled for implementation in other browser such as Chrome – is a step in the right direction (though it still requires extra handling for keyboard users). In the meantime, developers should be careful not to draw the wrong conclusions from touch support detection and avoid unwittingly locking out a growing number of potential multi-input users.

Further links

21 comments

Comments are now closed.

  1. Robert Kaiser wrote on April 9th, 2013 at 06:09:

    Touch+mouse devices aren’t a new introduction of “the recent months”. ASUS Transformer pads with keyboard “docks” have been on the market for a while. But those run on Android, and as you mention, that makes those mouse clicks be translated to touches, apparently.

    OTOH, touch screens exist for “traditional” desktops, further “blurring” the line (though with my current setup on Linux, all the touches there are translated to and reported as mouse clicks).

    It will be interesting where all this development is heading to, we surely need to support hybrid touch/mouse systems more and more.

  2. Patrick H. Lauke wrote on April 9th, 2013 at 06:26:

    Robert yes, I lumped those ASUS ones under the generic “Android with additional inputs” that I talked about.

    And you’re certainly right that there were also touchscreens for desktop in the past…but they were, I’d argue, more of an exception and not very common. I remember about 2 years ago spending ages trying to find a nice big touchscreen LCD, only to come up with just a handful of – at the time – overpriced products. Also, OS support was rather poor…I believe Win7 was the first to “properly” support touchscreens, and browsers like Firefox and Chrome – but not IE – even supported touch events on them…but again, more of an edge case at the time.

    Now, though, particularly with Win8 you almost can’t get a laptop/desktop that ISN’T also sporting a touchscreen, and now’s the time where naive optimisations just for touch (at the expense of any other inputs) will rear their ugly head most dramatically. IMHO, of course :)

  3. Andrea wrote on April 9th, 2013 at 06:34:

    oh well, as webdevelop I really don’t care what input device the user is using.

    I really want a set of event listener capable of detect: a single click/touch, the drag of an element, the drop of an element.

    And I don’t care if the user is using fingers or mouse or a laser sword :)

    Please browser vendor, quit all those strange events and start a new deal with a sort of interaction events.

  4. Patrick H. Lauke wrote on April 9th, 2013 at 07:04:

    Andrea, the Pointer Events spec comes fairly close, but IndieUI sounds like the one you’d really want http://www.w3.org/WAI/intro/indieui … unfortunately, the latter isn’t supported at all in any browser, if I recall correctly…

  5. Andrea wrote on April 9th, 2013 at 07:20:

    yes sure, IndieUI rocks :D

  6. Mary Branscombe wrote on April 9th, 2013 at 08:05:

    HP, Asus, Samsung have had combo touch/pen/notebook models for 2+ years as well; the long nose strikes again

  7. Stu Cox wrote on April 9th, 2013 at 08:30:

    Great article.

    However, `’ontouchstart’ in window` _technically_ only detects the API; not the presence of a touchscreen. It does currently work fairly reliably for modern touch devices**, but only because browsers disable the API when a touchscreen isn’t found.

    WebKit desperately didn’t want to do this; in fact Chrome 24 shipped with this API always-on, so that they could share code with the tablet & mobile versions more easily, but they then realised that it broke so many web pages which had assumed that this detect meant “is a touch device”, so they had to implement this pattern of disabling the API.

    Disabling the API isn’t great because it makes it hard to treat “having a touchscreen” as a dynamic feature… which it is, because you can connect/disconnect touchscreens, but if you do the API won’t be re-enabled mid session.

    That said, I’m not sure if Chrome will ever revert this change. I’m not entirely sure what that means for the whole situation.

    ** It sometimes false-positives on Chrome on Win 8 without a touchscreen; not sure why yet…

  8. Patrick H. Lauke wrote on April 9th, 2013 at 12:25:

    Stu, right you are…the feature detect just looks for the API itself, and relies on the fact that – as a de facto standard, at least in both Chrome/Webkit and Firefox – the API is not exposed (barring false positives, which I’ve not come across during my testing) if there is no actual touchscreen present on starting the browser.

    Indeed, this approach does not cater for the possibility of a touchscreen being added/removed while the browser is running, nor does it address further edge cases like multiple monitors, one of which is also a touchscreen, and what happens when you move the browser window from one to the other (or even worse, park it halfway). That’s where the origins of the Touch Events API (iOS) are clearly showing. I doubt that much effort will go into changing this in future, as I suspect that Pointer Events will be the ones seeing most focus for further development due to their slightly more robust mechanism.

    As you know, there is talk about Modernizr 3 having the ability to tie feature tests to callback functions, allowing for some form of dynamic querying. It’ll be “interesting” (read: a nightmare) to see how that pans out if, at its heart, the API that is normally queried here is static (possibly tying it to the last sent event rather than the classic feature detect, meaning that “a priori” it would still be impossible to determine touch capability, availability AND use).

    But again, this makes it even more important to simply NOT try to control/rely on this sort of detection and to design/develop in an input agnostic way to start with :)

  9. Patrick H. Lauke wrote on April 9th, 2013 at 12:30:

    Mary…I’m assuming the “long nose” thing is some oblique Pinocchio reference, meaning that I’ve been economical with the truth in my rant?

    True, I’ve simplified the argument slightly. Yes, there have been touchscreen/stylus/tablet/slate type devices for a while now, but purely subjectively I’d say they’ve been rare compared to the big push we’re seeing now with Win 8 devices, since 8 really does try to push for this touchscreen/multi-input way of working. Yes, Win 7, and even earlier versions, had limited touch capability (though I seem to recall not multitouch until Win 7, and in some cases styli etc behaved like mice/pen+tablet devices, not really identified as real touchscreens – and indeed IE9 and previous versions had no touch capability built in).

    Anyway, long story short: for Joe Public, I still contend that it’s only with Win 8 that the big push for economical, readily available multi-input devices prominently featuring touch is happening…

  10. Jake Archibald wrote on April 9th, 2013 at 22:42:

    Worth noting that “touchend” will fire at the end of a scroll in most browsers, which is rarely when you want to trigger a page interaction.

    Chrome is the only browser I’ve tested that doesn’t fire “touchend” after a scroll. The spec is completely ambiguous, so no one’s getting this bit wrong. Pointer Events specifies it properly.

  11. Dominykas wrote on April 10th, 2013 at 23:46:

    Let me see if I understand this correctly (for some reason I never thought about it _this_ way):

    * 300ms is supposedly so that the user can double tap (or presumably perform some other gesture)
    * we can avoid it by hooking into touch (and preventing default so that click doesn’t fire)
    * by doing so we disable the double tap (and presumably any other gestures)

    How does the 300ms even make sense then? What actual problem does it solve?

    And at the same time the evangelists are saying “don’t disable zoom [with user-scalable=no]“… We can’t win, can we?

  12. Patrick H. Lauke wrote on April 12th, 2013 at 04:39:

    “What actual problem does it solve?”

    it solves the problem of users wanting to zoom into the page regardless of whether or not they tapped on a link/button/etc. this may be on purpose (“i want to zoom in to better read the link text”), or it may just be that an activatable element happened to be there. regardless, it allows for zooming.

    the only reason why developers are obsessed with removing the delay is to make their web apps feel “more native” and snappy.

    “And at the same time the evangelists are saying “don’t disable zoom [with user-scalable=no]“… We can’t win, can we?”

    as always, there’s no clear-cut “win” scenario here. it depends what your priorities are. if you’ve optimised a web app (it uses nice large controls, and then disables zooming) and you want to shave off the delay…that’s your prerogative. yes, some low vision users will still have a problem with your choice of sizing. if you do care about these users, live with the 300ms delay (which yes, is noticeable, but not a deal breaker).

    on the other hand, there are discussions about browsers needing to offer a “always let me zoom, regardless of what the page has specified” extra setting (off by default). this would be the best of both worlds perhaps…

  13. Patrick H. Lauke wrote on April 14th, 2013 at 05:27:

    Btw, before I forget this little nugget of info: from my tests on a Windows 8 laptop with touchscreen, the only mainstream browser that does NOT support the touchscreen (i.e. that does not fire touch events nor support multitouch) at time of writing is Opera 12.x. Once Opera release their Chromium-based version, though, this will change (checking with a leaked pre-release build).

  14. Patrick H. Lauke wrote on April 14th, 2013 at 05:30:

    Also: that preview Chromium build with MS Pointer Events linked to from the blog post seems to fire events in a slightly messed-up order, not quite according to spec. See my comment here https://github.com/Modernizr/Modernizr/issues/869#issuecomment-15336713

  15. Dominykas wrote on April 14th, 2013 at 22:38:

    300ms _is_ a major deal breaker. For customers and for users. Any UX advice I’ve seen is “give feedback in 50-100ms if you want to make things feel instant”. Hence the choice for an average web developer is one of the two a) have a major deal breaker with 300ms b) disable zoom – which is less of a deal breaker, cause less people notice and even less complain (most just go away with a sigh, because we’ve trained our users to expect user-scalable=no)

    Since 300ms is mostly a problem due to not giving feedback – not the actual “wasted time”… one solution would be to feedback ontouchstart, without canceling the event, and do actual stuff onclick – it might just feel OK for the user. To be able to use this, we must be able to detect “touch canceled due to gesture” – can we?

    Note, that this is still extra work – and average developers, with their average customers, hate “extra work”.

  16. Patrick H. Lauke wrote on April 15th, 2013 at 00:26:

    Dominykas, the situation is not clear-cut either/or…either remove the delay and prevent zooming, or allow zooming and live with the 300ms.

    And to be clear: users have been using web on mobile for years now, and for regular websites they’ve been quite happy using those despite the 300ms. The issue really seems to come when developers try to make “native-like” web apps, where the difference does become noticeable.

    Anyway, you can use techniques – including the fastClick/catching touchend or the surgical inclusion of touch-action:none – as long as you don’t prevent double-taps or gestures on the *entire* page. This allows you *not* to lock down zooming.

    Your proposed “touch canceled due to gesture” wouldn’t work, as the whole point of the delay is to wait and see if a double-tap gesture is happening…and the only way to see if a user is going to double-tap is to wait a bit and listen for a second tap. Of course, users, can also zoom with a pinch/zoom gesture rather than double-tap. I’ve not tested it, but I suspect the current situation will be similar to what Jake Archibald mentioned in his comment concerning touchend firing even at the end of a scroll in all browsers other than Chrome, if one of your fingers is still over the element (an interesting edge case that I’ll add to my list of additional tests to explore). If that’s indeed the case, it should be possible to catch these situations by checking the number of touch points – if there’s still at least another point, or if the collection of changed touch points is greater than one, then the touchend was actually part of a multitouch action and should be ignored. Wondering if Pointer Events suffer the same weakness – if so, the fact that the arrays of all touches/changed touches doesn’t exist for Pointer Events, this would be a bit problematic and require some tracking of any touch points separately.

    “this is still extra work – and average developers [...] hate ‘extra work’”

    Welcome to web development, where – to get the best results – some extra work may often be required…we call it “being a professional” ;)

  17. Patrick H. Lauke wrote on April 15th, 2013 at 08:07:

    As I’m still actively working on some of the points touched on (hah) in his blog post, two more related links:

    - in Chrome/ium Dev Tools, if you enable faked touch events, the event order is wrong / different from real devices; this can throw your testing if you’re not careful or aware of the issue https://code.google.com/p/chromium/issues/detail?id=181204

    - Firefox in Windows 8 has some rather strange issues: preventDefault is currently being ignored (so if you can’t actually prevent synthetic mouse events and the final click being fired); mousemove fires multiple times together with touchmove (rather than only firing once after touch events have been processed); mousedown fires before touchend as well. https://bugzilla.mozilla.org/show_bug.cgi?id=861876

  18. Andrew D. Todd wrote on April 15th, 2013 at 13:41:

    Your rant is much too mild, and hardly to be dignified with the name of a rant at all. I therefore propose to go a good bit further. This is not to be considered a rant– it is to be considered a growl.

    Website design has an “Emperor’s New Clothes” problem. Large numbers of website designers have designed websites to show off, and to turn themselves into a faux-profession, with little or no interest in the needs of the client.This leads to websites which antagonize the end-user.

    The average businessman has a working instinctive knowledge of architecture, of interior design, of fashion design, things like that. He sees a storefront, and says, “right, sign the lease, put up a sign, and let’s move in!” He doesn’t let himself be buffaloed by a self-interested architect or building contractor into doing a new Taj Mahal at vast and indefinite expense, both of money and of time. There are a few kinds of businesses, such as an auto dealership or a gas station, and some kinds of restaurants, which really do require specialized architecture, but those are exceptions. Similarly, most men dress in the kinds of clothes they became accustomed to when they were young men. Thoreau’s advice about bewaring enterprises which require new clothes is probably applicable, the clothes being a kind of shorthand for a whole body of skills, aptitudes, and attitudes. More to the point, a competent businessman does not try to set about imposing an alien dress code on a large fraction of his customers.

    The problem is that most businessmen do not yet have this kind of knowledge for web design. Web designers are able to exploit them, and damage their business for their own personal advancement. The businessman never knows about the customers who turn away from the website in disgust and never contact him again.

    According to me, a website needs to work if the browser has Java, Javascript, and Flash all turned off, and some kind of warning dialog for cookies, if they are not turned off as well. If you need a cookie, you need to explain exactly what you are going to do with it. I should add that certain of the new HTML 5 features will have to be capable of being switched off, as they prove to be malware-targets. You cannot just order your customer’s customer to reconfigure his browser. That is an attitude of the utmost arrogance. There are plenty of other fish in the sea. I have repeatedly given up on businesses because their websites were so obviously zonked. Can you explain– in writing– why your new feature is important enough to justify chasing cash customers away?

    A web page is a document, like a word processor file. It is not any part of a web page’s business to detect inputs, or zoom in and out, or anything like that. It is the business of a browser to detect inputs and all that.

    I should add, in passing, that Amazon gets away with a few website faults that other businesses do not get away with, but that is mostly because of the efficiency of Amazon’s logistic system, with its hundred warehouses. If you do not have a hundred warehouses, I suggest you emulate Amazon’s good points, rather than its bad points.

  19. brothercake wrote on April 21st, 2013 at 02:56:

    I confess to being amazed that anyone applies events via testing.

    Several have touched on the fact that “foo in bar” tests are not reliable because they rely on assumptions of properties mappings that might not exist, and I would support that view. I’ve been writing JS for more than a decade, and this approach has only become possible at all in the last view years, because older browsers simply don’t map those properties the way you expect. There is no reason why touchstart events should map to window ontouchstart, it’s just a hangover from the days of DOM0 property mappings, which only stands up at all because vendors are trying to support all these wackass ways people have of writing JS!

    You don’t test for events, you bind all the events you care about, and use flags and preventDefault to filter out multiple events from the same interaction. This is the correct way to script for events; until recently it was the only way to script for events, and remains the most robust.

  20. J wrote on April 22nd, 2013 at 05:59:

    blah.addEventListener('touchend', function(e) {
    /* prevent delay and simulated mouse events */
    e.preventDefault();
    someFunction()
    });
    blah.addEventListener('click', someFunction());

    This code is wrong, as someFunction will get executed when the click event listener is added instead of when the click event is fired.

    The last line should be blah.addEventListener('click', someFunction);

  21. Patrick H. Lauke wrote on April 22nd, 2013 at 06:57:

    Oops, well spotted J…I fixed that typo/oversight.

Comments are closed for this article.