Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

Browser Input Events: Can We Do Better Than The Click?

Responding to user input is arguably the core of what we do as interface developers. In order to build responsive web products, understanding how touch, mouse, pointer and keyboard actions and the browser work together is key. You have likely experienced the 300-millisecond delay1 in mobile browsers or wrestled with touchmove2 versus scrolling3.

In this article we will introduce the event cascade and use this knowledge to implement a demo of a tap event that supports the many input methods while not breaking in proxy browsers such as Opera Mini.

Further Reading on SmashingMag: Link4

Overview Link

Three primary input methods are used to interact with the web today: digital cursors (mouse), tactile (direct touch or stylus) and keyboards. We have access to these in JavaScript through touch events10, mouse events11, pointer events12 and keyboard events13. In this article we are primarily concerned with touch- and mouse-based interactions, although some events have standard keyboard-based interactions, such as the click and submit events.

You have very likely already been implementing event handlers for touch and mouse events. There was a time in our not too distant pass when the recommended method was something akin to this:

/** DO NOT EVER DO THIS! */
$('a', ('ontouchstart' in window) ? 'touchend' : 'click', handler);

Microsoft has led the charge to create a better, future-facing event model with the “Pointer Events” specification. Pointer events are an abstract input mechanism that is now a W3C recommendation. Pointer events give the user agent (UA) flexibility to house numerous input mechanisms under one event system. Mouse, touch and stylus are all examples that easily come to mind today, although implementations extending to Myo14 or Ring15 are imaginable. While web developers seem to be really excited about this, not all browser engineers have felt the same. Namely, Apple and Google have decided not to implement pointer events at this time.

Google’s decision is not necessarily final, but there is no active work being done on pointer events. Our input and usage of pointer events through polyfills and alternative solutions will be part of the equation that could eventually tip the scale the other way. Apple made its statement against pointer events in 2012, and I am unaware of any more public response from Safari’s engineers.

The Event Cascade Link

When a user taps an element on a mobile device, the browser fires a slew of events. This action typically fires a series of events such as the following: touchstarttouchendmouseovermousemovemousedownmouseupclick.

This is due to backwards compatibility with the web. Pointer events take an alternative approach, firing compatibility events inline: mousemovepointerovermouseoverpointerdownmousedowngotpointercapturepointerupmouseuplostpointercapturepointeroutmouseoutfocusclick.

The event specification allows for UAs to differ in their implementation of compatibility events. Patrick Lauke and Peter-Paul Koch maintain extensive reference material on this topic, which is linked to in the resources section at the bottom of this article.

The following graphics show the event cascade for the following actions:

  1. an initial tap on an element,
  2. a second tap on an element,
  3. tapping off the element.

Please note: This event stack intentionally ignores where focus and blur events fit into this stack.

The event cascade on iOS devices for tapping on an element twice and then tapping away16

The event cascade on iOS devices for tapping on an element twice and then tapping away. (Image: Stephen Davis232017) (View large version18)
The event cascade on most Android 4.4 devices for tapping an element twice and then tapping away19

The event cascade on most Android 4.4 devices for tapping an element twice and then tapping away. (Image: Stephen Davis232017) (View large version21)
The event cascade on IE 11 (before compatibility touch events were implemented) for tapping an element twice and then tapping away.22

The event cascade on Internet Explorer 11 (before compatibility touch events were implemented) for tapping an element twice and then tapping away. (Image: Stephen Davis232017) (View large version24)

Applying the Event Cascade Link

Most websites built today for the desktop web “just work” because of the efforts of browser engineers. Despite the cascade looking gnarly, the conservative approach of building for mouse events as we previously have will generally work.

Of course, there is a catch. The infamous 300-millisecond delay is the most famous, but the interplay between scrolling, touchmove and pointermove events, and browser painting are additional issues. Avoiding the 300-millisecond delay is easy if:

  • we optimize only for modern Chrome for Android and desktop, which use heuristics such as <meta name="viewport" content="width=device-width"> to disable the delay;
  • we optimize only for iOS, and the user does a clear press, but not a quick tap and not a long tap — just a good, normal, clear press of an element (oh, it also depends on whether it’s in a UIWebView or a WKWebView — read FastClick’s issue on the topic25 for a good cry).

If our goal is to build web products that compete with native platforms in user experience and polish, then we need to decrease interaction response latency. To accomplish this, we need to be building on the primitive events (down, move and up) and creating our own composite events (click, double-click). Of course, we still need to include fallback handlers for the native events for broad support and accessibility.

Doing this requires no small amount of code or knowledge. To avoid the 300-millisecond (or any length of) delay across browsers, we need to handle the full interaction lifecycle ourselves. For a given {type}down event, we will need to bind all events that will be necessary to complete that action. When the interaction is completed, we will then need to clean up after ourselves by unbinding all but the starting event.

You, the website developer, are the only one to know whether the page should zoom or has another double-tap event it must wait for. If — and only if — you require the callback to be delayed should you allow a delay for the intended action.

In the following link, you will find a small, dependency-free tap demo to illustrate the effort required to create a multi-input, low-latency tap event. Polymer-gestures is a production-ready implementation of the tap and other events. Despite its name, it is not tied to the Polymer library in any way and can easily be used in isolation.

To be clear, implementing this from scratch is a bad idea. The following is for educational purposes only and should not be used in production. Production-ready solutions exist, such as FastClick26, polymer-gestures4127 and Hammer.js403028.

The Important Bits Link

Binding your initial event handlers is where it all begins. The following pattern is considered the bulletproof way to handle multi-device input.

/**
 * If there are pointer events, let the platform handle the input 
 * mechanism abstraction. If not, then it’s on you to handle 
 * between mouse and touch events.
 */

if (hasPointer) {
  tappable.addEventListener(POINTER_DOWN, tapStart, false);
  clickable.addEventListener(POINTER_DOWN, clickStart, false);
}

else {
  tappable.addEventListener('mousedown', tapStart, false);
  clickable.addEventListener('mousedown', clickStart, false);

  if (hasTouch) {
    tappable.addEventListener('touchstart', tapStart, false);
    clickable.addEventListener('touchstart', clickStart, false);
  }
}

clickable.addEventListener('click', clickEnd, false);

Binding touch event handlers could compromise rendering performance, even if they don’t do anything. To decrease this impact, binding tracking events in the starting event’s handler is often recommended. Don’t forget to clean up after yourself and unbind the tracking events in your action-completed handlers.

/**
 * On tapStart we want to bind our move and end events to detect 
 * whether this is a “tap” action.
 * @param {Event} event the browser event object
 */

function tapStart(event) {
  // bind tracking events. “bindEventsFor” is a helper that automatically 
  // binds the appropriate pointer, touch or mouse events based on our 
  // current event type. Additionally, it saves the event target to give 
  // us similar behavior to pointer events’ “setPointerCapture” method.

  bindEventsFor(event.type, event.target);
  if (typeof event.setPointerCapture === 'function') {
    event.currentTarget.setPointerCapture(event.pointerId);
  }

  // prevent the cascade
  event.preventDefault();
  
  // start our profiler to track time between events
  set(event, 'tapStart', Date.now());
}

/**
 * tapEnd. Our work here is done. Let’s clean up our tracking events.
 * @param {Element} target the html element
 * @param {Event} event the browser event object
 */

function tapEnd(target, event) {
  unbindEventsFor(event.type, target);
  var _id = idFor(event);
  log('Tap', diff(get(_id, 'tapStart'), Date.now()));
  setTimeout(function() {
    delete events[_id];
  });
}

The rest of the code should be pretty self-explanatory. In truth, it’s a lot of bookkeeping. Implementing custom gestures requires you to work closely with the browser event system. To save yourself the pain and heartache, don’t do this à la carte throughout your code base; rather, build or use a strong abstraction, such as Hammer.js403028, the Pointer Events31 jQuery polyfill or polymer-gestures.

Conclusion Link

Certain events that used to be very clear are now filled with ambiguity. The click event used to mean one thing and one thing only, but touchscreens have complicated it by needing to discern whether the action is a double-click, scroll, event or some other OS-level gesture.

The good news is that we now understand much better the event cascade and the interplay between a user’s action and the browser’s response. By understanding the primitives at work, we are able to make better decisions in our projects, for our users and for the future of the web.

What unexpected problems have you run into when building multi-device websites? What approaches have you taken to solve for the numerous interaction models we have on the web?

Additional Resources Link

(da, al, ml)

Footnotes Link

  1. 1 http://ionicframework.com/blog/hybrid-apps-and-the-curse-of-the-300ms-delay/
  2. 2 https://docs.google.com/document/d/12k_LL_Ot9GjF8zGWP9eI_3IMbSizD72susba0frg44Y/
  3. 3 http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
  4. 4 https://www.smashingmagazine.com/2012/08/javascript-events-responding-user/#further-reading-on-smashingmag
  5. 5 https://www.smashingmagazine.com/2016/12/the-not-so-secret-powers-of-the-mobile-browser/
  6. 6 https://www.smashingmagazine.com/2014/09/building-simple-cross-browser-offline-todo-list-indexeddb-websql/
  7. 7 https://www.smashingmagazine.com/2012/08/javascript-events-responding-user/
  8. 8 https://www.smashingmagazine.com/2010/02/the-seven-deadly-sins-of-javascript-implementation/
  9. 9 https://www.smashingmagazine.com/2010/04/seven-javascript-things-i-wish-i-knew-much-earlier-in-my-career/
  10. 10 http://www.w3.org/TR/touch-events/
  11. 11 http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mouseevents
  12. 12 http://www.w3.org/TR/pointerevents/
  13. 13 http://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#keys
  14. 14 https://www.thalmic.com/en/myo/
  15. 15 http://logbar.jp/ring/en/
  16. 16 https://www.smashingmagazine.com/wp-content/uploads/2015/02/01-ios-opt.png
  17. 17 https://dribbble.com/stephendavis
  18. 18 https://www.smashingmagazine.com/wp-content/uploads/2015/02/01-ios-opt.png
  19. 19 https://www.smashingmagazine.com/wp-content/uploads/2015/03/02-android-opt.png
  20. 20 https://dribbble.com/stephendavis
  21. 21 https://www.smashingmagazine.com/wp-content/uploads/2014/09/03-android-os-distribution-opt.png
  22. 22 https://www.smashingmagazine.com/wp-content/uploads/2015/02/03-pointer-opt.png
  23. 23 https://dribbble.com/stephendavis
  24. 24 https://www.smashingmagazine.com/wp-content/uploads/2015/02/03-pointer-opt.png
  25. 25 https://github.com/ftlabs/fastclick/issues/262#issuecomment-56363076
  26. 26 https://github.com/ftlabs/fastclick/
  27. 27 https://github.com/Polymer/polymer-gestures
  28. 28 http://hammerjs.github.io/
  29. 29 https://github.com/Skookum/smashing-input-events/blob/gh-pages/taps.js#L1
  30. 30 http://hammerjs.github.io/
  31. 31 https://github.com/jquery/PEP
  32. 32 http://arstechnica.com/information-technology/2015/02/pointer-events-finalized-but-apples-lack-of-support-still-a-deal-breaker/
  33. 33 http://patrickhlauke.github.io/getting-touchy-presentation/
  34. 34 https://www.youtube.com/watch?v=QYLC8o3U_XY
  35. 35 http://timkadlec.com/2015/02/apples-web/
  36. 36 http://timkadlec.com/2013/11/Avoiding-the-300ms-click-delay-accessibly/
  37. 37 http://www.quirksmode.org/mobile/tableTouch.html
  38. 38 http://blogs.msdn.com/b/ie/archive/2014/09/05/making-the-web-just-work-with-any-input.aspx
  39. 39 https://github.com/ftlabs/fastclick
  40. 40 http://hammerjs.github.io/
  41. 41 https://github.com/Polymer/polymer-gestures
  42. 42 https://github.com/jquery/PEP

↑ Back to top Tweet itShare on Facebook

Dustan Kasten is a developer advocate at Skookum Digital Works. He spends his time exploring ideas behind browser-based UI application development. He still believes the web has the potential to build compelling interfaces that can compete with native platforms.

  1. 1

    Great job Dustan!

    1
  2. 3

    Jon Concepcion

    March 21, 2015 8:08 am

    Well documented post!

    2
  3. 5

    miranda mtyeku

    March 22, 2015 12:42 am

    I prefer browser as it’s easier to get any downloads from Internet

    0
  4. 6

    Let the double click die a slow death… it’s a pattern now one ever got right the first time they used it.

    1
    • 7

      Dustan Kasten

      March 23, 2015 2:20 pm

      Ha. Too bad it couldn’t be a quicker death; but since iOS overloads double-tap with scroll behavior, I suspect it will be around in some sense for quite a bit longer.

      0
  5. 8

    Did I miss the news about apple buying android from google?

    https://www.smashingmagazine.com/wp-content/uploads/2015/02/02-android-opt.png

    4
    • 9

      Dustan Kasten

      March 23, 2015 2:21 pm

      Good catch! I have a new image in the queue. That should be corrected shortly. :)

      0
  6. 10

    Really interesting article.
    I have come to very similar conclusions and been battling with input across multiple platforms and devices for a while.

    I tried around 6 of the best libraries I could found that make various claims.
    Unfortunately many of them have issues in certain areas.
    Especially where it comes to scrolling and chrome on windows.
    I found Hammer.js to be quite problematic. Not to shit on all the hard work the contributors of that project, however it tries to do everything and some of the basics are don’t work very well across all the devices I tested.

    I think to add to your conclusion, this is a very complex subject which I think the browser vendors should be working to normalise. The Chomium team has re-opened discussions on the PointerEvents API. So maybe that’ll eventually be the solution, just not in it’s current form. https://code.google.com/p/chromium/issues/detail?id=162757

    0
    • 11

      Dustan Kasten

      March 23, 2015 7:06 pm

      Thanks, Rob. I agree. Browser vendors are working hard on this space. It’s definitely not easy :)

      Have you tried https://github.com/Polymer/polymer-gestures in production yet? I used hammer 1.x in production and ran into a few issues. From looking at the code, Hammer 2.0 seems like a really good step in the right direction.

      0
  7. 12

    Good news! Blink has announced intent to implement pointer events. https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/ODWmcKNQl0I
    Following so close on the heels of this piece, it might be worth updating to reflect that.

    1
  8. 13

    Fawad Hassan

    March 30, 2015 8:17 pm

    Apple is hurting the open web in same way like Microsoft used to do.

    1
  9. 14

    Shouldn’t it be very easy for the browser makers to distinguish a single pixel click from an multipixel area touch? Couldn’t they provide the information they actually gather to the service providers that otherwise struggle a great deal with the *diminshed* information?

    1

↑ Back to top