Building products and services for mobile and web? We're writing about how to stand out and make products that help people.

Working with touch events

Last month I was attending the jQuery Europe conference in Vienna with the Mobiscroll team.
There was a session called Getting touchy which gave an insight into touch events and talked about why we need them.
There is a lot of ground that the presentation covers, so make sure to check out the slides. I would like to build on top of it and share my experience on the topic.

So why do we need touch events?

We don’t always need to use touch events in our apps or websites. Turns out that mostly we can get away of using the regular click events because mobile browsers emulate the mouse events rather well.
While emulation works in many cases, the functionality is limited.

The usual problems are:

Delayed event dispatch: mouse events are usually fired with a delay, which makes the app feel unresponsive.

Mousemove doesn’t track, only a single emulated mousemove event is fired. This makes impossible to make complex UI interfaces with gestures.

There are a ton of resources on the web targeting these issues, so I’m not reinventing the wheel here, I will just share my own personal experience on how I combined and extended different solutions to match our needs while building Mobiscroll.

The click delay

As you probably know, there is a delay between the actual tap and the firing of the click event. I’m not going into details on why this happens, you can read about it in the slides mentioned before.

A common technique to prevent the delay is to bind the handler to both touchend and click events. The challenge here is to prevent the so called “phantom click”, meaning that if your handler was called on touch end, don’t execute it again when the click event is emulated by the browser. The proposed solution here is to call e.preventDefault() either on touchstart or touchend which will prevent the firing of emulated mouse events.

However I find this problematic because:

Calling it on touchstart will kill native page scroll

Calling it on touchend will kill momentum scroll on some devices

It does not prevent at all emulated events on Android 4.x stock browsers

The solution we are using in Mobiscroll does the following:

Attach touchstart, touchend and click events

Remember start and end coordinates

Call the handler e.preventDefault() on touchend, but only if movement was less then 20px in any direction (so the user did not have the intention to scroll)

Set a flag to prevent executing the handler again in the emulated click event

Start a timeout which will clear the flag in case when the click event was not emulated

Working with gestures

When we started building the Listview, we imagined a widget with heavy gesture support. And this is where it shines.

We needed support of following:

Use native vertical scrolling

Handle left and right swipe gestures, disable page scroll during swipe gestures

Handle tap

Handle tap and hold (activate sort mode)

Native scroll

If we want to keep using native touch scrolling, this involves that we cannot call e.preventDefault() on any of the touch events unconditionally.

Horizontal swipe

We need to listen to the touchmove event and decide on the fly whether it will be a vertical or horizontal swipe. If it’s horizontal swipe, kill the page scroll with calling e.preventDefault(). The following thresholds are used:

If the horizontal movement is more than 7px, we consider it a swipe

If the vertical movement is more than 10px, we consider it a scroll

Tap

We need to decide if the user is tapping on a list item or intends to swipe or scroll. If movement was less than 5px in any direction tap is being honored. We also need to take care of the duplicate firing of the events, so a flag is being set – similar to the one we used for the click delay.

Tap & hold

On touchstart we will start a timer which activates the “sort/reorder” mode after a delay. This timer is being reset in case of a scroll, swipe, or touchend.

Let’s take a look at our event handlers. Also note that we attach the touch events and the mousedown to the element itself, while the mousemove and mouseup events are attached to the document element dynamically and removed at the end. That is because they behave differently: the touchmove and touchend will still be fired if the finger leaves the element, while the mousemove and mouseend event will not fire once the mouse pointer has left the element.

Problems that are still unsolved

Android ICS

On Android ICS if no preventDefault is called on touchstart or the first touchmove, further touchmove events and the touchend will not be fired. As a workaround we need to decide in the first touchmove if this is a scroll (so we don’t call preventDefault) and then manually trigger touchend – see the code above.

Windows Phone

In WP8 there is no way to prevent native scroll on the fly. To be able to listen to touch events, you need to set the following css property:

touch-action: none;

However this will kill all default behavior, like native page scroll. Fortunately this can be fine tuned, so for the Listview we use the following:

touch-action: pan-y;

Which tells to browser that vertical swipe will be handled by the browser, while our code will take care of the horizontal swipe. Unfortunately sorting won’t be working, because it will not prevent native scroll while dragging an element. So in WP8 the only way to implement sorting is to use a “sort handle” element which has the touch-action: none; rule applied. So when the user “picks” up an item from the sort handle, we know he intends to reorder.

Firefox Mobile

In Firefox Mobile the native scroll can be killed only if preventDefault() is called on the touchstart event. Unfortunately at touchstart we don’t really know if we want scroll or not. This has two consequences:

sort will work with the above mentioned sort handle only (by calling preventDefault() on touchstart if dragged by the handle)

while swiping an item left or right vertical scroll will still work

These issues can easily disappear in upcoming browser updates or releases, until then we need to come up with workarounds.

What are your hacks and workarounds for dealing with complex gestures? Let us know in the comment section below.

Start building better products

Sign up and get posts like this in your inbox. We respect your time and will send you only the good stuff!