Touch Event Handling in JavaScript
Jakob Jenkov |
Touch Event Listeners
Handling touch events in JavaScript is done by adding touch event listeners to the HTML elements to handle touch events for. This is done similarly to adding a click listener:
var theElement = document.getElementById("theElement"); theElement.addEventListener("touchend", handlerFunction, false); function handlerFunction(event) { }
Touch events are somewhat more complex than mouse events. Like with a mouse you can listen for touch down, touch move, touch end etc. but a user only has one mouse pointer, whereas a user may touch the screen with multiple fingers at the same time.
You can listen for the following touch events:
Event | Description |
---|---|
touchstart | Fired when the user puts one or more fingers on the screen. |
touchmove | Fired while the user moves the finger(s) over the screen while touching it. |
touchend | Fired when the user stops touching the screen. |
touchleave | Fired when the user moves their fingers outside of the area listening for touch events. |
touchcancel | Fired when a touch gesture is canceled, for instance if the user moves her fingers outside of the screen itself. |
Not all browsers may fire all of these events (I have had problems getting Chrome to fire touch leave events).
Handling Taps
A tap is like a mouse click. The user taps a button or link just like they would click it with a mouse.
Mobile browsers will typically convert a tap on a button, link etc. to a mouse click after 300 milliseconds (ms), to make regular web apps work, even if the web apps is not directly listening for touch events. The browser waits 300ms to see if the user performs any more advanced touch gestures, before firing the click event. This is done to make sure that it is actually a click event that should get fired.
These 300ms makes your web app feel laggy compared to native apps. Web apps already have a disadvantage compared to native apps, and those 300ms just makes that worse. Therefore we want to listen for touch events in touch enabled browsers.
It is not enough to just listen for touch events though. You want your app to be usable in desktop browsers too (which are not touch enabled), so you have to listen for both click and touch events. Here is how that is done:
var theElement = document.getElementById("theElement"); theElement.addEventListener("mouseup", tapOrClick, false); theElement.addEventListener("touchend", tapOrClick, false); function tapOrClick(event) { //handle tap or click. event.preventDefault(); return false; }
When you listen for both touch and click events, you have a slight problem in touch enabled browsers. Even if a touch event is fired immediately after a tap, an additional click event is still fired after 300ms. That means that your listener function will get called twice!
To avoid the double calls to the listener function, you call the event.preventDefault()
function
on the event
object. This disables the browsers default behaviour, and lets your code handle
the touch event. This disables the firing of the click event 300ms after the touch event. It also disables
the default action of a tap, whatever action the browser might have as default for a tap.
In a browser without touch support the touchend
event never fires. Only the mouseup
event will fire. Thus, your listener code will still work. It is also common in click listeners (mouseup
)
to disable the browsers default action for the click. For instance, if you click a submit button inside a form,
the browsers default action is to submit the form. We don't want that either. Thus, it is just fine to
prevent the browser's default actions from a click listener too.
If you need different handling of touch and click events, you can just implement two different event listeners, and make each handler do its work the way it needs to.
Handling Touches
As mentioned earlier, the user may use multiple fingers when touching a mobile device. Therefore touch events
may contain information about more than one touche. The touch information is stored inside the
event.changedTouches
property of the event
object. Here is how you access them:
function touchHandler(event) { var touches = event.changedTouches; for(var i=0; i < event.changedTouches.length; i++) { var touchId = event.changedTouches[i].identifier; var x = event.changedTouches[i].pageX; var y = event.changedTouches[i].pageY; } }
As you can see, each touch has its own identifier, x and y coordinate.
The identifier is used to identify each touch event between events being fired. For instance, if
the user touches the screen and then moves the finger (like you do when "swiping" the screen),
you can track the changes in the touch via the touch identifier. You will need to copy the
touch information in the touchstart
event, and then match against that in the
touchmove
and / or touchend
events. Here is an example that shows
how to do that:
theElement.addEventListener("touchstart", touchStartHandler, false); theElement.addEventListener("touchend", touchEndHandler, false); var touchesInAction = {}; function touchStartHandler(event) { var touches = event.changedTouches; for(var j = 0; j < touches.length; j++) { /* store touch info on touchstart */ touchesInAction[ "$" + touches[j].identifier ] = { identifier : touches[j].identifier, pageX : touches[j].pageX, pageY : touches[j].pageY }; } } function touchEndHandler(event) { var touches = event.changedTouches; for(var j = 0; j < touches.length; j++) { /* access stored touch info on touchend */ var theTouchInfo = touchesInAction[ "$" + touches[j].identifier ]; theTouchInfo.dx = touches[j].pageX - theTouchInfo.pageX; /* x-distance moved since touchstart */ theTouchInfo.dy = touches[j].pageY - theTouchInfo.pageY; /* y-distance moved since touchstart */ } /* determine what gesture was performed, based on dx and dy (tap, swipe, one or two fingers etc. */ }
Notice how the touchstart
event handler function copies the information from the
touch objects in the event object. Some browsers may reuse these touch objects, so simply storing
the original touch object may not work. The values in it might be overridden by later touches.
The touchend
event handler function accesses the copied touch object and calculates
how far each touch has moved since it started (dx, dy).
In order to respond to gestures like pinching, you may have to do the dx, dy calculation in
the touchmove
event instead of the touchend
event. When reacting to
a pinch the web app should react during pinching, not when the pinch ends.
In order to respond to more advanced multi touch gestures, you may have to mark each touch
object copy with status flags like "ongoing" / "ended", and perhaps the time it ended.
When a touchend
event is fired, you can then iterate all the touch copies
to see if there are still ongoing touches. If no more touches are ongoing,
iterate all the touches and see which
finished within the last 500ms - 1000ms. Those touches were probably all part of the last
gesture. Now you can start calculating what gesture was made, based on dx, dy etc.
For really advanced gestures, like moving in a circle, you may even need to collect the
whole touch path as an array during touchmove
. Thus you have all the touch
coordinates of the whole movement available when the touch ends.
Tweet | |
Jakob Jenkov |