In the mobile application world, you still sometimes need to build plumbing for things to work correctly. One area I put effort into recently was getting the right visual feedback on a Touch device (namely, iPhone).
With a standard mouse device, the feedback is straightforward, the:active CSS pseudo class makes it easy to give instant visual feedback. For example, a web designer might want to darken the background when a user presses a mouse button on a widget. The web browser simply activates the pseudo class when the button is pressed and deactivates it when the mouse is released. This can be achieved with a couple of lines of CSS: .mybutton:active {
background-color: 0;
}
Touch interfaces such as iPhone require more dedicated treatment in some situations. In an iPhone, a touch-and-move gesture will initiate scrolling, which should also deactivate the pseudo class. But this behavior is not built in. As such it needs to be reinvented.
Based on jQTouch, I re-implemented this behavior. The original code set a timeout handler to activate the class (.active, instead of :active) after a brief delay to ensure a scroll does not trigger .active. However, this means a quick touch and release will not generate the active.
style.css:
.mybutton.active {
background-color: 0;
}
script.js:
$(tapSelectors.join(', ')).live(START_EVENT, touchstart);
function touchstart(e) {
var $el = null;
var startX, startY, startTime;
var deltaX, deltaY, deltaT;
var endX, endY, endTime;
var swipped = false, tapped = false, moved = false, inprogress = false;
$el.bind(MOVE_EVENT, handlemove).bind(END_EVENT, handleend);
if ($.support.touch) {
$el.bind(CANCEL_EVENT, handlecancel);
} else {
$(document).bind('mouseout', handleend);
}
} function unbindEvents($el) {
$el.unbind(MOVE_EVENT, handlemove).unbind(END_EVENT, handleend);
if ($.support.touch) {
$el.unbind(CANCEL_EVENT, handlecancel);
} else {
$(document).unbind('mouseout', handlecancel);
}
} function updateChanges() {
var first = $.support.touch? event.changedTouches[0]: event;
deltaX = first.pageX - startX;
deltaY = first.pageY - startY;
deltaT = (new Date).getTime() - startTime;
}
function handlestart(e) {
inprogress = true, swipped = false, tapped = false, moved = false, timed = false;
startX = $.support.touch? event.changedTouches[0].clientX: event.clientX;
startY = $.support.touch? event.changedTouches[0].clientY: event.clientY;
startTime = (new Date).getTime();
endX = null, endY = null, endTime = null;
deltaX = 0;
deltaY = 0;
deltaT = 0; if (!!$el) {
$el.removeClass('active');
}
$el = $(e.currentTarget); // Let's bind these after the fact, so we can keep some internal values
bindEvents($el); setTimeout(function() {
handlehover(e);
}, 50);
}; function handlemove(e) {
updateChanges();
var absX = Math.abs(deltaX);
var absY = Math.abs(deltaY); if (absX >= 1 || absY >= 1) {
moved = true;
}
if (absY <= 5) {
if (absX > absY && (absX > 35) && deltaT < 1000) {
inprogress = false;
$el.removeClass('active');
unbindEvents($el); swipped = true;
$el.trigger('swipe', {direction: (deltaX < 0) ? 'left' : 'right', deltaX: deltaX, deltaY: deltaY });
}
} else {
// moved too much, can't swipe anymore
inprogress = false;
$el.removeClass('active');
unbindEvents($el);
}
}; function handleend(e) {
updateChanges();
var absX = Math.abs(deltaX);
var absY = Math.abs(deltaY); inprogress = false;
unbindEvents($el);
if (!tapped && (absX <= 1 && absY <= 1)) {
tapped = true;
setTimeout(function() {
$el.trigger('tap');
}, 10); /* give a chance other touch to end */
setTimeout(function() {
$el.removeClass('active');
}, 1000);
} else {
$el.removeClass('active');
e.preventDefault();
}
}; function handlecancel(e) {
inprogress = false;
$el.removeClass('active');
unbindEvents();
}; function handlehover(e) {
timed = true;
if (tapped) {
// flash the selection
$el.addClass('active');
setTimeout(function() {
$el.removeClass('active');
}, 1000);
} else if (inprogress && !moved) {
$el.addClass('active');
}
}; handlestart(e);
}; // End touch handler
Find the full code listing here: http://github.com/beedesk/jQTouch/blob/master/jqtouch/jqtouch.js