Preface
Reading Zepto's touch module source code is to understand the specific implementation of tap, and then write the touch module for my own utility library my.js
(my.js is a mobile page utility library tailored according to business needs, does not support win phone)
I. Zepto and Tap-Through/Click-Through Problems
Zepto previously had click-through problems, fixed the in-page click-through problem in 2014 (or was it 12/13?), but currently (2015-8-22) still exists cross-page click-through problem
P.S. For detailed information about click-through problems, please see AnYuQingYang: Mobile Page Click-Through Problem Solution
II. Source Code Analysis
Zepto's touch module source code is as follows, with detailed comments (IE-related parts omitted):
// Zepto.js
// (c) 2010-2015 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
;(function($) {
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, // Timeout ID
longTapDelay = 750,
gesture
// Judge swipe direction, return Left, Right, Up, Down
function swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >=
Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
}
// Long press
function longTap() {
longTapTimeout = null
if (touch.last) {
touch.el.trigger('longTap')
touch = {}
}
}
// Cancel long press
function cancelLongTap() {
if (longTapTimeout) clearTimeout(longTapTimeout)
longTapTimeout = null
}
// Cancel all
function cancelAll() {
if (touchTimeout) clearTimeout(touchTimeout)
if (tapTimeout) clearTimeout(tapTimeout)
if (swipeTimeout) clearTimeout(swipeTimeout)
if (longTapTimeout) clearTimeout(longTapTimeout)
touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
touch = {}
}
// IE touch event?
function isPrimaryTouch(event) {
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary
}
// (ie) mouse event?
function isPointerEventType(e, type) {
return (e.type == 'pointer' + type ||
e.type.toLowerCase() == 'mspointer' + type)
}
// Entry point
$(document).ready(function() {
var now, delta, deltaX = 0,
deltaY = 0,
firstTouch, _isPointerType
// IE gesture
if ('MSGesture' in window) {
gesture = new MSGesture()
gesture.target = document.body
}
$(document)
// Handle IE gesture end
.bind('MSGestureEnd', function(e) {
var swipeDirectionFromVelocity =
e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null;
if (swipeDirectionFromVelocity) {
touch.el.trigger('swipe')
touch.el.trigger('swipe' + swipeDirectionFromVelocity)
}
})
// Handle finger touch
.on('touchstart MSPointerDown pointerdown', function(e) {
// Exclude non-touch devices
if ((_isPointerType = isPointerEventType(e, 'down')) &&
!isPrimaryTouch(e)) return
// Get starting position data
firstTouch = _isPointerType ? e : e.touches[0]
// Reset end coordinates
if (e.touches && e.touches.length === 1 && touch.x2) {
// Clear out touch movement data if we have it sticking around
// This can occur if touchcancel doesn't fire due to preventDefault, etc.
touch.x2 = undefined
touch.y2 = undefined
}
// Judge user action type
now = Date.now()
delta = now - (touch.last || now) // Time difference from last touch
touch.el = $('tagName' in firstTouch.target ? // Element finger touched
firstTouch.target : firstTouch.target.parentNode)
touchTimeout && clearTimeout(touchTimeout) // Reset touch event handler's Timeout ID
// Record starting coordinates
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
// Judge double tap
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
touch.last = now // Update last touch time
// Register long press event handler ID (triggers long press after 750ms, will definitely judge cancel when finger leaves)
longTapTimeout = setTimeout(longTap, longTapDelay)
// Support IE gesture recognition
// adds the current touch contact for IE gesture recognition
if (gesture && _isPointerType) gesture.addPointer(e.pointerId);
})
// Handle finger swipe
.on('touchmove MSPointerMove pointermove', function(e) {
// Exclude non-touch devices
if ((_isPointerType = isPointerEventType(e, 'move')) &&
!isPrimaryTouch(e)) return
// Read starting coordinates
firstTouch = _isPointerType ? e : e.touches[0]
// Cancel long press event handler
cancelLongTap()
// Record current point coordinates
touch.x2 = firstTouch.pageX
touch.y2 = firstTouch.pageY
// Accumulate swiped distance
deltaX += Math.abs(touch.x1 - touch.x2)
deltaY += Math.abs(touch.y1 - touch.y2)
})
// Handle finger leave
.on('touchend MSPointerUp pointerup', function(e) {
// Exclude non-touch devices
if ((_isPointerType = isPointerEventType(e, 'up')) &&
!isPrimaryTouch(e)) return
// Cancel long press event handler
cancelLongTap()
// swipe
// Judge swipe action (horizontal or vertical distance from start to end exceeds 30px)
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
// Register long press event handler ID (immediately prepare to execute long press)
swipeTimeout = setTimeout(function() {
touch.el.trigger('swipe') // Trigger long press
// Trigger up|down|left|right long press
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
// Clear data, this touch ends
touch = {}
}, 0)
// normal tap
// Normal light touch
else if ('last' in touch) // If last contact time is recorded
// don't fire tap when delta position changed by more than 30 pixels,
// for instance when moving to a point and back to origin
// If from touch to lift, horizontal and vertical distance swiped in between doesn't exceed 30px — is normal light touch
if (deltaX < 30 && deltaY < 30) {
// delay by one tick so we can cancel the 'tap' event if 'scroll' fires
// ('tap' fires before 'scroll')
// Immediately prepare to execute light touch, not execute immediately is to be able to cancel executing light touch when scroll
tapTimeout = setTimeout(function() {
// trigger universal 'tap' with the option to cancelTouch()
// (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
// Trigger global tap, cancelTouch() can cancel singleTap, doubleTap events, for faster light touch response
var event = $.Event('tap')
event.cancelTouch = cancelAll
touch.el.trigger(event)
// trigger double tap immediately
// Immediately trigger doubleTap
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap')
touch = {}
}
// trigger single tap after 250ms of inactivity
// Trigger singleTap after 250ms
else {
touchTimeout = setTimeout(function() {
touchTimeout = null
if (touch.el) touch.el.trigger('singleTap')
touch = {}
}, 250)
}
}, 0)
} else {
// If swiped in a circle and back to starting point, discard event data, no processing
touch = {}
}
// Reset horizontal, vertical swipe distance
deltaX = deltaY = 0
})
// when the browser window loses focus,
// for example when a modal dialog is shown,
// cancel all ongoing events
// When browser window loses focus, cancel all event handling actions
.on('touchcancel MSPointerCancel pointercancel', cancelAll)
// scrolling the window indicates intention of the user
// to scroll, not tap or swipe, so cancel all ongoing events
// Cancel all event handling actions when scroll triggers
$(window).on('scroll', cancelAll)
})
// Register custom events
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
'doubleTap', 'tap', 'singleTap', 'longTap'
].forEach(function(eventName) {
$.fn[eventName] = function(callback) {
return this.on(eventName, callback)
}
})
})(Zepto)
III. Implementation Principle of tap Event
tap event is simulated through touch events, tap means "light touch", much faster than click (about 300ms). Specific implementation details won't be elaborated (comments in source code above are very clear), here say something off-topic
Implementing tap alone is very easy, dozens of lines of code is enough, but to ensure remaining functions work normally requires manually implementing a complete set of action events:
-
tap
-
singleTap
-
doubleTap
-
longTap
-
swipe
-
swipeLeft
-
swipeRight
-
swipeUp
-
swipeDown
If simply implementing tap, then swipe, double tap, long press and various other actions will all fail, Zepto's touch module only implements tap and swipe related actions, doesn't support complex gestures, if need to support complex gestures, recommend selecting hammer.js, hammer provides a complete set of gesture support (Note: hammer also has click-through problem, still need to manually handle this problem)
No comments yet. Be the first to share your thoughts.