Nice question! +1
This problem has nothing to do with FastClick, but FastClick does make the solution to your problem more complex. So I will stick to pure JavaScript and raw Touch Events )
On a mobile touch device the implementation of the webview is significantly different from the desktop web browser for reasons specific to the platform.
One feature that is important in this case is momentum scrolling in the webview.
Momentum Scrolling is a hardware accelerated feature of the device. So when a scrollable area is touched on the screen, the hardware identifies the touch and attaches a 200 millisecond countdown timer to the scrollable area, that when triggered, puts the area into a hardware accelerated scroll. When the hardware is in the state it does not broadcast touch events because they are specific to the hardware accelerated scroll. The timer can be cancelled, and momentum scrolling prevented by using preventDefault on the touch event within the provided 200 milliseconds.
Here is how I approach this problem. I assume you are using native scroll i.e overflow:scroll
.
The method is to attach a touchstart
event to the element you want touchable.
Once the touchstart
event has fired, the event handler attaches touchend
, touchmove
and touchcancel
events to the target element. A setTimout timer is initiated that removes the newly added events after 140ms.
Here are some clips for my production code:
I have two event methods for adding and removing events from collections:
var eventify = (function () {
function eventify(type, el, callback, phase) {
phase = phase || false;
if ((el.constructor.toString().contains('NodeList')) || (el.constructor.toString().contains('HTMLCollection'))) {
[].forEach.call(el, function (element) {
if (!element.hasEvent(type)) {
element.addEvent(type);
HTMLElement.prototype.addEventListener.apply(element, [type, callback, phase]);
}
});
} else {
if (!el.hasEvent(type)) {
el.addEvent(type);
HTMLElement.prototype.addEventListener.apply(el, [type, callback, phase]);
}
}
return callback;
}
return eventify
})();
var uneventify = (function () {
function uneventify(type, el, callback) {
if ((el.constructor.toString().contains('NodeList')) || (el.constructor.toString().contains('HTMLCollection'))) {
[].forEach.call(el, function (element) {
if (element.hasEvent(type)) {
element.removeEvent(type);
HTMLElement.prototype.removeEventListener.apply(element, [type, callback]);
}
});
} else {
if (el.hasEvent(type)) {
el.removeEvent(type);
HTMLElement.prototype.removeEventListener.apply(el, [type, callback]);
}
}
}
return uneventify
})();
Then I have a tapify method for tap events on the scroller:
var tapify = (function () {
function tapify(el, callback) {
eventify('touchstart', el, function (e) {
var that = this;
var start = e.pageY;
var target = e.target;
function dynamicEvents() {
var endfn = eventify('touchend', target, function (evt) {
e.preventDefault();
e.stopImmediatePropagation();
evt.preventDefault();
evt.stopImmediatePropagation();
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
callback && callback(target);
});
var cancelfn = eventify('touchcancel', target, function (evt) {
e.preventDefault();
e.stopImmediatePropagation();
evt.preventDefault();
evt.stopImmediatePropagation();
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
callback && callback(target);
});
var movefn = eventify('touchmove', target, function (evt) {
var distance = start - evt.pageY;
if (distance > 20) {
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
uneventify('touchmove', el, movefn);
}
});
setTimeout(function () {
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
}, 140);
}
if (global.isIos) setTimeout(function () {
dynamicEvents();
}, 60);
else dynamicEvents();
}, false);
}
return tapify;
})();
I use global.isIos to identify the target device. Android stops sending touch events to webview ater 200ms.
Then to attach the event to a element or a collection of elements, use :
tapify(document.querySelectorAll('button'), function (e) {
//your event handler here!!
});
Hope this helps