FastClick: native-like tapping for touch apps
The JavaScript APIs for handling touch events and gestures in JavaScript are simple and intuitive enough to grasp on first try, and the increasingly excellent support for web standards in Webkit browsers means we can create highly touch optimised web apps that look and feel like native apps. But that doesn’t mean there won’t be any hitches when you get deeper into web app development, and one of those hitches only becomes apparent when users stop thinking of your app as a website.
Probably because touchscreen devices weren’t nearly as popular in 1995 as they are today, JavaScript DOM events tend to reflect the mouse ‘click’ paradigm, where each event neatly corresponds to a single, deliberate click of the button. Take the onClick event, for example. How does that translate to a touchscreen device? Not easily, as it turns out.
Treating a ‘tap’ as a ‘click’ is the practical approach. On iOS at least, there’s no onTap event – onClick has been repurposed for that role. But in order to properly handle multiple-touch gestures like pinching or even double-taps, some compromises have to be made. One of these is the roughly 300ms delay between a tap and the firing of a click event, which can make your apps feel laggy and unresponsive even when it’s not technically so.
The technique we use to get around this problem is to track all TouchStart events in our app and fire a click event as soon as we receive a TouchEnd event (unless some application-specific exception is satisfied).
As we refined our fast-clicking code, we turned it into a small and efficient library, which we call FastClick. This code has been tried and tested by hundreds of thousands of users, and so far has proved to be very robust. We’d love to know what others are doing to address this challenge and find ways of improving our own approach, and to help kick off that process, we’re open-sourcing FastClick today. We’d encourage you to try it out, and let us know what you think.
To use FastClick, instantiate it on the layer you’d like to be fast-clickable – we use document.body because we want all our buttons and links to receive fast clicks. In your event listeners, ‘click’ events synthesised by FastClick will have the forwardedTouchEvent property set to true.
If you use buttons and iOS-style menus in your app then there’s a good chance your interface feels unresponsive on touchscreen devices. Here’s a simple example of how we solve that problem for a single button by instantiating FastClick on it:
<button class=”fastclick” onclick=”someHandler()”>Fast click</button>
<button onclick=”someHandler()”>Slow click</button>
<script type='text/javascript'>
var button = document.querySelector(".fastclick");
new FastClick(button);
</script>
In the example, the button with a fastclick class will have its click handler called as soon as your finger is lifted off the screen, while tapping on any other buttons on the page will trigger the same handler after a noticeable delay. Try the live demo.
Unfortunately, the select element doesn’t behave normally when receiving (synthesised) programmatic clicks, so if you apply FastClick to an element that contains selects, FastClick will ignore clicks on the select and allow the normal click event to fire.
If you want any other elements (besides selects) in your FastClick layer to receive non-programmatic clicks, you’ll need to use one of two classes: clickevent or touchandclickevent. For any clickable element in a FastClick layer, tapping the element will cause different effects depending on how you use these classes:
- No class: The element will receive only a programmatic click from FastClick. The default click event triggered by the user will be suppressed.
clickevent: The element will receive only the default click event, and will be ignored by FastClicktouchandclickevent: The element will receive both the default click event AND a programmatic click from FastClick (the FastClick one will be triggered first). This is only safe if your handler’s action is idempotent.
Here’s an example of all three:
<div class="fastclick">
<button onclick="someHandler()">
Will receive programmatic click event
</button>
<button class="clickevent" onclick="someHandler()">
Will receive non-programmatic click event
</button>
<button class="touchandclickevent" onclick="someHandler()">
Will receive both click events
</button>
</div>
<script type='text/javascript'>
var button = document.querySelector(".fastclick");
new FastClick(button);
</script>
When is this useful? Try the other live demo we’ve built using a click event handler that attempts to trigger focus on an input element. iOS will only allow focus to be triggered on other elements within a handler function if the event that triggered it was non-programmatic.
We’ll be posting updates and answering questions on this blog. If the interest reaches a stage where FastClick could benefit from the developer community, we’ll move to a public repository host. But for now, concentrate on giving your users the best response time they can get – download FastClick (or minified) and give it a go. Its free to use in all your apps, and licensed under the MIT license.
11 Comments
How would this work when creating elements in the DOM? I’m trying to make a popup calendar work faster but FastClick doesn’t seem to initiate using:
var fclicks = document.querySelector(“a”);
new FastClick(fclicks);
or… (jquery)
$(“a”).live(‘click’, function(){
$(this).FastClick();
}):
The above obviously doesn’t work since it initiates click before fastClick, but anyway. Any idea to make it work with dynamically created elements?
Hi Tommie,
FastClick is already designed to function like a controller, so there’s no need to use jQuery live. Instead, instantiate it on the document body:
new FastClick(document.body);
It will add capturing listeners to the body then use document.elementFromPoint to find the target element, so changes to the DOM should be handled seamlessly.
Matt
Thanks for the fast answer, but activating it on the body makes my auto completer non-functioning since it then selects the first LI onClick. Therefor it would be better to activate fastClick only on elements that needs fastclick and not on the body.
Doable?
Doable, yes, but certainly not ideal. Think about it this way: it’s better to have one negative exception than 200 positive exceptions.
If the only problematic element is the autocompleter layer, then the solution is to introduce an exception for that rather than instantiate FastClick on every other element individually.
Do you have a page that I can look at to see how FastClick is interfering with the autocompleter? Then I’d be able to propose a proper solution.
Yeah, that was what i was thinking as well but it’s also better to have something that works then not to though.
Example:
http://m.jade.se/t/tmatt.html
A bit messy since i’m playing around with it. I initiate FastClick at line 103.
Fixed it by editing my autoComplete script to add the class “clickevent” to the LI’s. Thanks for your help and quick responses though.
Great that you fixed it yourself. Don’t hesitate to come back if you have any other questions!
[...] http://assanka.net/content/tech/2011/08/26/fastclick-native-like-tapping-for-touch-apps/ RedditBufferShareEmailPrintFacebookDiggStumbleUpon [...]
Great script! Unfortunately on Chrome 17 and 18 the touch detection returns false positive.
See http://modernizr.github.com/Modernizr/touch.html
Hope you can fix it!
Hi George – Thanks. I’ve fixed it so that the type detection doesn’t return a false positive in Chrome >= 17.
Thanks Matt! I wonder only why even if the touch detection failed, all the mouse clicks were prevented… I thought this should really happen only after a real touch … Google also suggest an interesting approach http://code.google.com/intl/nl-NL/mobile/articles/fast_buttons.html