As we all know, due to historical reasons, mobile click events have a delay of around 300ms. Zepto’s Touch module solves this problem and also provides swipe events.
Star: Reading-Zepto
Source code version
The source code read in this article is zepto1.2.0
GitBook
The reading – zepto
Implemented events
; ['swipe'.'swipeLeft'.'swipeRight'.'swipeUp'.'swipeDown'.'doubleTap'.'tap'.'singleTap'.'longTap'].forEach(function(eventName){
$.fn[eventName] = function(callback){ return this.on(eventName, callback) }
})Copy the code
As you can see from the code above, Zepto implements the following events:
- Swipe: It’s an event
- SwipeLeft: swipeLeft event
- SwipeRight: Right swiping event
- SwipeUp: Swipes up events
- SwipeDown: Indicates a slide down event
- DoubleTap: Screen double-click event
- Tap: screen click event, than
click
Faster event response - SingleTap: Screen click event
- LongTap: Long press event
Shortcuts are registered for each event.
Internal methods
swipeDirection
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')}Copy the code
What is returned is the sliding method.
X1 is the starting point of the x axis, x2 is the ending point of the X axis, y1 is the starting point of the y axis, and y2 is the ending point of the y axis.
Here are several sets of triadic expressions. First, compare the sliding distance on the X-axis and the Y-axis. If the sliding distance on the X-axis is larger than the sliding distance on the Y-axis, it is left to right, otherwise, it is up and down.
On the X-axis, swipe Left to return Left if the starting position is larger than the ending position, otherwise swipe Right to return Right.
On the Y-axis, if the starting position is larger than the ending position, it will slide Up and return Up, otherwise it will slide Down and return Down.
longTap
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture
function longTap() {
longTapTimeout = null
if (touch.last) {
touch.el.trigger('longTap')
touch = {}
}
}Copy the code
Trigger the long press event.
The Touch object holds information during touch.
Before the longTap event is triggered, the variable longTapTimeout of the save timer is released. If last exists in the touch object, the longTap event is triggered. Last stores the time of the last touch. Finally, reset the touch to an empty object for next use.
cancelLongTap
function cancelLongTap() {
if (longTapTimeout) clearTimeout(longTapTimeout)
longTapTimeout = null
}Copy the code
Untriggers the longTap event.
If there is a timer that triggers longTap, clearing the timer prevents the longTap event from being triggered.
Finally, you also need to set the longTapTimeout variable to null and wait for garbage collection.
cancelAll
function cancelAll() {
if (touchTimeout) clearTimeout(touchTimeout)
if (tapTimeout) clearTimeout(tapTimeout)
if (swipeTimeout) clearTimeout(swipeTimeout)
if (longTapTimeout) clearTimeout(longTapTimeout)
touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
touch = {}
}Copy the code
Clear execution of all events.
It clears all relevant timers and finally sets the touch object to NULL.
isPrimaryTouch
function isPrimaryTouch(event){
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH)
&& event.isPrimary
}Copy the code
Whether the primary contact.
The primary contact is when pointerType is touch and isPrimary is true. PointerType can be Touch, Pen, or mouse, where only finger touches are handled.
isPointerEventType
function isPointerEventType(e, type){
return (e.type == 'pointer'+type ||
e.type.toLowerCase() == 'mspointer'+type)
}Copy the code
Whether a pointerEvent is triggered.
In the lower version of mobile Internet Explorer, only PointerEvent is implemented, and TouchEvent is not implemented, so this is needed to judge.
Events trigger
The overall analysis
$(document).ready(function(){
var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
$(document)
.bind('MSGestureEnd'.function(e){... }) .on('touchstart MSPointerDown pointerdown'.function(e){... }) .on('touchmove MSPointerMove pointermove'.function(e){... }) .on('touchend MSPointerUp pointerup'.function(e){... }) .on('touchcancel MSPointerCancel pointercancel', cancelAll)
$(window).on('scroll', cancelAll)Copy the code
To start with a few variables, now is used to store the current time, delta is used to store the time difference between two touches, deltaX is used to store the displacement on the x axis, deltaY is used to store the displacement on the Y axis, firstTouch is used to store information about the initial touch point, _isPointerType Saves the judgment result of whether the event is a pointerEvent.
The events triggered by Zepto are calculated from touch, Pointer, and Guesture events of IE, depending on the situation. These events are bound to the Document.
IE Gesture Event handling
IE gesture use, need to go through three steps:
- Creating a gesture object
- Specify the target element
- Specifies a pointer to process for gesture recognition
if ('MSGesture' in window) {
gesture = new MSGesture()
gesture.target = document.body
}Copy the code
This code contains the first two steps.
on('touchstart MSPointerDown pointerdown'.function(e){... if (gesture && _isPointerType) gesture.addPointer(e.pointerId) }Copy the code
This is the third step, using the addPointer method to specify which pointer to process.
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)
}
})Copy the code
The next step is to analyze gestures, and in Gesture you only swipe.
VelocityX and velocityY are the velocities along the x and y axes, respectively. Swipe is taking 1 or -1 as the critical point.
Swipe events are triggered if the direction of swipe exists, and also swipe events with directions are triggered.
start
on('touchstart MSPointerDown pointerdown'.function(e){
if((_isPointerType = isPointerEventType(e, 'down')) &&
!isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]
if (e.touches && e.touches.length === 1 && touch.x2) {
touch.x2 = undefined
touch.y2 = undefined
}
now = Date.now()
delta = now - (touch.last || now)
touch.el = $('tagName' in firstTouch.target ?
firstTouch.target : firstTouch.target.parentNode)
touchTimeout && clearTimeout(touchTimeout)
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
touch.last = now
longTapTimeout = setTimeout(longTap, longTapDelay)
if (gesture && _isPointerType) gesture.addPointer(e.pointerId)
})Copy the code
Filter out non-touch events
if((_isPointerType = isPointerEventType(e, 'down')) &&
!isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]Copy the code
The result of isPointerEventType is also saved to _isPointerType to determine whether it is a PointerEvent.
The judgment here is that only PointerEvent and TouchEvent are handled, and the isPrimary of TouchEvent must be true.
Because TouchEvent supports multi-touch, only the firstTouch point is taken and stored in the firstTouch variable.
Reset destination coordinates
if (e.touches && e.touches.length === 1 && touch.x2) {
touch.x2 = undefined
touch.y2 = undefined
}Copy the code
The endpoint coordinates need to be updated if recording is required.
Normally, the Touch object would clear on touchEnd or Cancel, but if the user calls preventDefault themselves, it might not clear.
One thing I’m not sure about here is why it’s only done when touches are taken. Don’t you need to empty multiple touch points?
Record information about the touch point
now = Date.now()
delta = now - (touch.last || now)
touch.el = $('tagName' in firstTouch.target ?
firstTouch.target : firstTouch.target.parentNode)
touchTimeout && clearTimeout(touchTimeout)
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageYCopy the code
Now is used to save the current time.
Delta is used to save the time between two clicks to process the double click event.
Touch. El is used to hold the target element. If the target is not a tag node, take the parent node as the target element. This occurs when you click on a pseudo-class element.
If touchTimeout exists, the timer is cleared to avoid repeated triggering.
Touch. X1 and touch. Y1 save x and y coordinates respectively.
Double-click the event
if (delta > 0 && delta <= 250) touch.isDoubleTap = trueCopy the code
You can clearly see that Zepto treats the time between two clicks less than 250ms as a doubleTap event, setting isDoubleTap to True.
Long press event
touch.last = now
longTapTimeout = setTimeout(longTap, longTapDelay)Copy the code
Set touch.last to the current time. This allows you to record the time difference between the two clicks.
At the same time, start the long press event timer. As can be seen from the above code, the long press event will be triggered after 750ms.
move
on('touchmove MSPointerMove pointermove'.function(e){
if((_isPointerType = isPointerEventType(e, 'move')) &&
!isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]
cancelLongTap()
touch.x2 = firstTouch.pageX
touch.y2 = firstTouch.pageY
deltaX += Math.abs(touch.x1 - touch.x2)
deltaY += Math.abs(touch.y1 - touch.y2)
})Copy the code
The move event handles two things: recording the coordinates of the destination and calculating the displacement from the start to the destination.
Note that cancelLongTap is also called to clear the long-press timer to avoid triggering the long-press event. Because it’s moving, it’s definitely not a long press.
end
on('touchend MSPointerUp pointerup'.function(e){
if((_isPointerType = isPointerEventType(e, 'up')) &&
!isPrimaryTouch(e)) return
cancelLongTap()
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
swipeTimeout = setTimeout(function() {
if (touch.el){
touch.el.trigger('swipe')
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
}
touch = {}
}, 0)
else if ('last' in touch)
if (deltaX < 30 && deltaY < 30) {
tapTimeout = setTimeout(function() {
var event = $.Event('tap')
event.cancelTouch = cancelAll
if (touch.el) touch.el.trigger(event)
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap')
touch = {}
}
else {
touchTimeout = setTimeout(function(){
touchTimeout = null
if (touch.el) touch.el.trigger('singleTap')
touch = {}
}, 250)}},0)}else {
touch = {}
}
deltaX = deltaY = 0
})Copy the code
swipe
cancelLongTap()
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
swipeTimeout = setTimeout(function() {
if (touch.el){
touch.el.trigger('swipe')
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
}
touch = {}
}, 0)Copy the code
Clear execution of the longTap timer immediately upon entering end.
As you can see, a distance of more than 30 between the beginning and the end is judged to be a swipe event.
Swipe events in the corresponding direction are triggered immediately after triggering the event.
Note that the Swipe event does not fire immediately when the End series event is triggered. Instead, a 0ms timer is set for the event to fire asynchronously. What is the use of this? We’ll talk about that later.
tap
else if ('last' in touch)
if (deltaX < 30 && deltaY < 30) {
tapTimeout = setTimeout(function() {
var event = $.Event('tap')
event.cancelTouch = cancelAll
if (touch.el) touch.el.trigger(event)
}, 0)}else {
touch = {}
}
deltaX = deltaY = 0Copy the code
If the long press event is triggered, the touch object will be cleared, and the tap event will not be triggered.
If it is not swipe and there is no last, only touch is cleared without triggering any events.
DeltaX and deltaY are reset to 0 at the end.
When a TAP event is triggered, a cancelTouch method is added to the event, which can be used to cancel all events.
Again, setTimeout is used to asynchronously trigger events.
doubleTap
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap')
touch = {}
}Copy the code
This isDoubleTap is determined at start, as analyzed above, and the doubleTap event is raised at end.
Therefore, you can see that two TAP events are emitted before the doubleTap event is emitted.
singleTap
touchTimeout = setTimeout(function(){
touchTimeout = null
if (touch.el) touch.el.trigger('singleTap')
touch = {}
}, 250)Copy the code
If it is not doubleTap, the singleTap event will be triggered 250ms after the TAP event.
cancel
.on('touchcancel MSPointerCancel pointercancel', cancelAll)Copy the code
When the Cancel event is received, the cancelAll method is called to cancel all events.
scroll
$(window).on('scroll', cancelAll)Copy the code
As you can see from the previous analysis, all event firing is asynchronous.
Because in scroll, it must only want to respond to the event of scroll, and the asynchronous trigger is to cancel the event during Scroll and when the outside world calls the cancelTouch method.
series
- Read the code structure of Zepto source code
- Internal method of reading Zepto source code
- Read Zepto source tool function
- Read Zepto source code magic $
- Read Zepto source set operation
- Read Zepto source collection element search
- Read Zepto source operation DOM
- Read Zepto source style operation
- Read Zepto source properties operation
- Read the Event module of Zepto source code
- Read Zepto source IE module
- Read the Zepto source Callbacks module
- Read the Deferred module of Zepto source code
- Read Zepto source Ajax module
- Read the Assets module of Zepto source code
- Read Zepto source Selector module
reference
- Zepto Touch library source code analysis
- PointerEvent
- Pointer events
- TouchEvent
- Touch
- GestureEvent
- MSGestureEvent
- Step by step DIY Zepto library, zepto source code 8- touch module
- Zepto source code learning -06 Touch
- Zepto source code touch.js
- addPointer method.aspx)
License
CC BY-NC-ND 4.0
Finally, all articles will be simultaneously sent to the wechat public account, welcome to pay attention to, welcome to comment:
Akik The opposite corner