Archive for September, 2014

Double-tap interactions

While click events are seamlessly supported on touch devices, double-click events are not and there is no equivalent double-tap event available. However, double-tap interactions can be captured by listening for multiple, subsequent touchend events. The code below shows how to do this, with the basis for a double-tap being:

  • Two touchend events, on a certain element, both occurring within a certain time interval (300ms)
  • Two touchend events, on a certain element, both occurring within a certain distance from each other (24px)

I’ve come across a lot of code that addresses the first point (see double tap on mobile safari), but the second point is just as important because almost all touchscreens are multitouch and a two-handed posture with a phone or tablet is not uncommon. With a two-handed posture, and an element large enough to span the screen, it’s easy to hit two different areas of the element (on opposite ends of the screen) in rapid succession – a double-tap would be detected, even though two distant points on the element were touched, unless the distance between the points is taken into account.

Ideally, both the interval and distance should be user-defined settings at the device/operating-system level, similar to the way setting the double-click speed is, but lacking such support, hacking it in at the application-level is the only viable option.


// Handler for when a double-tap (on a touchscreen) or double-click (with a mouse) is detected
var dblTapHandler = function (x, y) {

// Do stuff...

}

$(document).ready(
function() {


// Listen for double-click events for desktop/mouse-based interactions
$('.ia-dbltap-area').on('dblclick', function (e) {
// Call handler
dblTapHandler(e.pageX, e.pageY);
});


// Listen for touchend events for touch-based interactions
$('.ia-dbltap-area').on('touchend', function(e) {

var dblTapRadius = 24; // radius (in pixels) of the area in which we expect the 2 taps for a double-tap
var dblTapSpeed = 300; // interval (in milliseconds) in which we expect the 2 taps for a double-tap

if(e.originalEvent.changedTouches.length <= 0) {
return false; // we have nothing to work with
}

var dblTapDetected = false; // flag specifying if we detected a double-tap
var areaElem = $(this); // element in which this touchend event has occured

// Position of the touch
var x = e.originalEvent.changedTouches[0].pageX;
var y = e.originalEvent.changedTouches[0].pageY;

var now = new Date().getTime();

// Check if we have stored data for a previous touch (indicating we should test for a double-tap)
if(areaElem.data('last-touch-time')) {

lastTouchTime = areaElem.data(
'last-touch-time');

// Compute time since the previous touch
var timeSinceLastTouch = now - lastTouchTime;

// Get the position of the last touch on the element
var lastX = areaElem.data('last-touch-x');
var lastY = areaElem.data('last-touch-y');

// Compute the distance from the last touch on the element
var distFromLastTouch = Math.sqrt( Math.pow(x-lastX,2) + Math.pow(y-lastY,2) );

// Check if:
// 1. If the time since the last touch is within the specified double-tap interval (dblTapSpeed)
// 2. The distance from the last touch is within the specified double-tap radius (tapRadius)
if(timeSinceLastTouch <= dblTapSpeed && distFromLastTouch <= dblTapRadius) {

// Flag that we detected a double tap
dblTapDetected = true;

// Call handler
dblTapHandler(x, y);

// Remove last touch info from element
areaElem.data('last-touch-time', '');
areaElem.data(
'last-touch-x', '');
areaElem.data(
'last-touch-y', '');
}

}


if(!dblTapDetected) { // A double-tap wasn't detected

// Store time and position of this touch on the element
// (Next touch may be a double-tap, we can use this info to determine if it is)
areaElem.data('last-touch-time', now);
areaElem.data(
'last-touch-x', x);
areaElem.data(
'last-touch-y', y);
}

});

});

The demo below shows the code in actions along with a bit of SVG to render where the user double-clicked or double-touched div.ia-dbltap-area: