Skip to content

Commit 93ed42a

Browse files
authored
Update fastclick.js
The value of lastClickTime only gets updated in onTouchEnd, after the value of lastClickTime is already compared to touchEndTime. The value of lastClickTime is originally based on the event.timeStamp. When the event.timeStamp comes through as negative, the new value of touchStartTime can no longer be validly compared to the timeStamp, and the function will (often) return before lastClickTime can get updated. The same issue will manifest, until after the value of touchEndTime exceeds the original value of lastClickTime. Setting lastClickTime to 0 when switching over to get a value based on getTime will help to avoid this. When switching over to the non-event.timeStamp value, lastClickTime should only be set to 0 once. All time comparisons from then on out should be made against a point in time, obtained when switching over, and compare milliseconds since that time. This work was based on a pull request from @lasselaakkonen and is a fix to issue 549. ftlabs#550 ftlabs#549
1 parent 536219b commit 93ed42a

File tree

1 file changed

+51
-8
lines changed

1 file changed

+51
-8
lines changed

lib/fastclick.js

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@
102102
*/
103103
this.tapTimeout = options.tapTimeout || 700;
104104

105+
/**
106+
* The check for whether to track touch event start from event.timeStamp (default) or current time (to handle negative timestamp issue)
107+
*
108+
* @type boolean
109+
*/
110+
this.isUsingEventTimeStamp = true;
111+
112+
/**
113+
* The last cick event time stamp
114+
*
115+
* @type number
116+
*/
117+
this.lastClickTime = 0;
118+
119+
/**
120+
* The base time to use for comparison, when not tracking touch by event.timeStamp
121+
*
122+
* @type number
123+
*/
124+
this.nonEventTrackingTimeBase = null;
125+
105126
if (FastClick.notNeeded(layer)) {
106127
return;
107128
}
@@ -389,7 +410,21 @@
389410
* @returns {boolean}
390411
*/
391412
FastClick.prototype.onTouchStart = function(event) {
392-
var targetElement, touch, selection;
413+
var targetElement, touch, selection, touchTimeStamp;
414+
415+
if (this.isUsingEventTimeStamp && event.timeStamp > -1) {
416+
touchTimeStamp = event.timeStamp;
417+
} else {
418+
if (this.isUsingEventTimeStamp) {
419+
// Set the base time minus twice the tap delay, to avoid the user seeing a non-response this first time through.
420+
// This would occur when comparing touchTimeStamp - lastClickTime, which would be roughly 0, and expecting it not to be < this.tapDelay.
421+
this.nonEventTrackingTimeBase = ((new Date()).getTime() - (this.tapDelay * 2));
422+
this.lastClickTime = 0;
423+
this.isUsingEventTimeStamp = false;
424+
}
425+
426+
touchTimeStamp = ((new Date()).getTime() - this.nonEventTrackingTimeBase);
427+
}
393428

394429
// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
395430
if (event.targetTouches.length > 1) {
@@ -435,14 +470,14 @@
435470
}
436471

437472
this.trackingClick = true;
438-
this.trackingClickStart = event.timeStamp;
473+
this.trackingClickStart = touchTimeStamp;
439474
this.targetElement = targetElement;
440475

441476
this.touchStartX = touch.pageX;
442477
this.touchStartY = touch.pageY;
443478

444479
// Prevent phantom clicks on fast double-tap (issue #36)
445-
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
480+
if ((touchTimeStamp - this.lastClickTime) < this.tapDelay) {
446481
event.preventDefault();
447482
}
448483

@@ -519,26 +554,34 @@
519554
* @returns {boolean}
520555
*/
521556
FastClick.prototype.onTouchEnd = function(event) {
522-
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
557+
var forElement, trackingClickStart, targetTagName, scrollParent, touch, touchEndTime, targetElement = this.targetElement;
558+
559+
if (this.isUsingEventTimeStamp) {
560+
touchEndTime = event.timeStamp;
561+
} else {
562+
// iOS 11.3+ and Safari 11.1+ can return negative event.timeStamp values after resuming.
563+
// https://github.com/ftlabs/fastclick/issues/549
564+
touchEndTime = ((new Date()).getTime() - this.nonEventTrackingTimeBase)
565+
}
523566

524567
if (!this.trackingClick) {
525568
return true;
526569
}
527570

528571
// Prevent phantom clicks on fast double-tap (issue #36)
529-
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
572+
if ((touchEndTime - this.lastClickTime) < this.tapDelay) {
530573
this.cancelNextClick = true;
531574
return true;
532575
}
533576

534-
if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
577+
if ((touchEndTime - this.trackingClickStart) > this.tapTimeout) {
535578
return true;
536579
}
537580

538581
// Reset to prevent wrong click cancel on input (issue #156).
539582
this.cancelNextClick = false;
540583

541-
this.lastClickTime = event.timeStamp;
584+
this.lastClickTime = touchEndTime;
542585

543586
trackingClickStart = this.trackingClickStart;
544587
this.trackingClick = false;
@@ -571,7 +614,7 @@
571614

572615
// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
573616
// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
574-
if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
617+
if ((touchEndTime - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
575618
this.targetElement = null;
576619
return false;
577620
}

0 commit comments

Comments
 (0)