As we all know, for historical reasons, click events on the mobile end will have a delay of about 300 ms. Zepto's touch module solves the problem of click delay on the mobile end, and also provides sliding swipe events.
Read the Zepto source series on github. Welcome star: reading-zepto
Source version
The source code for this article is zepto1.2.0
GitBook
Implemented events
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){ $.fn[eventName] = function(callback){ return this.on(eventName, callback) } })
As you can see from the above code, Zepto implements the following events:
- swipe: Sliding events
- swipeLeft: Left Slide Event
- swipeRight: Slide to the right
- swipeUp: Slide Up Event
- swipeDown: Downward sliding event
- Double Tap: Screen Double-click Event
- tap: Screen click events respond faster than click events
- singleTap: Screen click event
- Long Tap: Long Press Event
And a shortcut is registered for each event.
Internal method
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') }
Returns a sliding method.
x1 is the starting point coordinate of x axis, x2 is the end point coordinate of x axis, y1 is the starting point coordinate of y axis, y2 is the end point coordinate of y axis.
There are many sets of ternary expressions. First, the sliding distance on the x-axis and the y-axis is compared. If the sliding distance on the x-axis is larger than that on the y-axis, it will slide left and right, otherwise it will slide up and down.
On the x-axis, if the starting point is larger than the end point, slide to the left, return to Left, otherwise slide to the right, return to Right.
On the y-axis, if the starting point is larger than the end point, it slides upward and returns to Up, otherwise it slides downward and returns to Down.
longTap
var touch = {}, touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, longTapDelay = 750, gesture function longTap() { longTapTimeout = null if (touch.last) { touch.el.trigger('longTap') touch = {} } }
Trigger the long press event.
The touch object stores the information in the process of touching.
Before triggering the longTap event, the variable longTapTimeout that saves the timer is released. If there is last in the touch object, the longTap event is triggered. Last saves the last touch time. Finally, reset touch to an empty object for next use.
cancelLongTap
function cancelLongTap() { if (longTapTimeout) clearTimeout(longTapTimeout) longTapTimeout = null }
Untrigger the longTap event.
If there is a timer that triggers the longTap, clearing the timer prevents the triggering of the longTap event.
Finally, you 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 = {} }
Clear the execution of all events.
In fact, it is to clear all the relevant timers, and finally set the touch object to null.
isPrimaryTouch
function isPrimaryTouch(event){ return (event.pointerType == 'touch' || event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary }
Is it the main contact?
The main contact is when the pointerType is touch and isPrimary is true. The pointerType can be touch, pen, and mouse, where only finger touch is handled.
isPointerEventType
function isPointerEventType(e, type){ return (e.type == 'pointer'+type || e.type.toLowerCase() == 'mspointer'+type) }
Is the pointerEvent triggered?
In the low version of mobile IE browser, only PointerEvent is implemented, but TouchEvent is not implemented, so this judgment is needed.
Event triggering
Holistic 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)
First, several variables are explained. now is used to save the current time, delta is used to save the time difference between two touches, deltaX is used to save the displacement on the x axis, deltaY is used to save the displacement on the y axis, first Touch is used to save the information of the initial touchpoint, and _isPointerType is used to save the judgment result of pointerEvent.
As you can see from the above, Zepto triggers events from touch, pointer or IE guesture events, calculated according to different circumstances. These events are bound to document s.
Processing of IE Gesture Events
IE's gesture use needs three steps:
- Create gesture objects
- Specify target elements
- Specifies the pointer to be processed for gesture recognition
if ('MSGesture' in window) { gesture = new MSGesture() gesture.target = document.body }
This code contains the first two steps.
on('touchstart MSPointerDown pointerdown', function(e){ ... if (gesture && _isPointerType) gesture.addPointer(e.pointerId) }
This is the third step, using the addPointer method to specify the pointer to be processed.
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) } })
The next step is to analyze gestures, which only deal with swipe events in Gesture.
velocityX and velocityY are the rates on the x-axis and y-axis, respectively. Here we use 1 or - 1 as the critical point to judge the direction of swipe.
If the direction of swipe exists, the swipe event is triggered, and the directed swipe event is also 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) })
Filter out non-touch events
if((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0]
The judgment result of isPointerEventType is also saved in _isPointerType to determine whether it is a PointerEvent or not.
The judgement here is that only PointerEvent and TouchEvent are processed, and TouchEvent's isPrimary must be true.
Because TouchEvent supports multiple touches, only the first point of the touch is stored in the first Touch variable.
Reset terminal coordinates
if (e.touches && e.touches.length === 1 && touch.x2) { touch.x2 = undefined touch.y2 = undefined }
If records are needed, the endpoint coordinates need to be updated.
Normally, the touch object will be empty when touchEnd or cancel is touched, but if the user calls preventDefault, etc., there may be no empty case.
It's not clear why touches are emptied only when they are operated on a single point. Does it not need to be cleared when there are multiple contact points?
Record contact information
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
now is used to save the current time.
delta is used to save the time interval between two clicks and to handle double-click events.
touch.el is used to save the target element. Here's a judgment. If the target is not a label node, take the parent node as the target element. This will occur when you click on the pseudo-Class element.
If touchTimeout exists, the timer is cleared to avoid repeated triggers.
touch.x1 and touch.y1 save X-axis coordinates and y-axis coordinates respectively.
dblclick
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
It's clear that Zepto sets isDoubleTap to true when the time interval between two clicks is less than 250ms as a doubleTap event.
Long press event
touch.last = now longTapTimeout = setTimeout(longTap, longTapDelay)
Set touch.last to the current time. This allows you to record the time difference between two clicks.
At the same time, the long press event timer starts. As can be seen from the above code, the long press event will trigger 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) })
The move event handles two things, one is to record the coordinates of the endpoint, and the other is to calculate the displacement between the starting point and the endpoint.
Note that cancelLongTap is also called here to clear the long press timer and avoid triggering the long press event. Because of the movement, 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 })
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)
When you enter the end, clear the execution of the longTap timer immediately.
It can be seen that when the distance between the starting point and the end point exceeds 30, it will be judged as a swipe sliding event.
After the swipe event is triggered, the swipe event in the corresponding direction is triggered immediately.
Note that the swipe event is not triggered immediately when the end series of events trigger, but set up a timer of 0ms to trigger the event asynchronously. What's the use of this? I'll talk about it 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 = 0
Finally, we can see the key point. First, we can judge whether last exists. From the start, we can see that if the start is triggered, last certainly exists, but if the long press event is triggered, the touch object will be empty, and then the tap event will not be triggered again.
If it's not a swipe event, and there's no last, just empty the touch and don't trigger anything.
In the end, deltaX and deltaY will be reset to 0.
When the tap event is triggered, cancelTouch method is added to the event, which can be used by the outside world to cancel the execution of all events.
setTimeout is also used to trigger events asynchronously.
doubleTap
if (touch.isDoubleTap) { if (touch.el) touch.el.trigger('doubleTap') touch = {} }
This isDoubleTap is determined at start, which has been analyzed above, and triggers the doubleTap event at end.
Therefore, you can know that two tap events are triggered before the doubleTap event is triggered.
singleTap
touchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250)
If it is not doubleTap, the singleTap event will be triggered 250ms after the tap event triggers.
cancel
.on('touchcancel MSPointerCancel pointercancel', cancelAll)
When a cancel event is received, the cancelAll method is called to cancel the triggering of all events.
scroll
$(window).on('scroll', cancelAll)
As can be seen from the previous analysis, all event triggers are asynchronous.
Because when scroll is used, you want to respond only to scrolling events, and asynchronous triggering is used to cancel events when cancelTouch method is called in the scroll process and outside.
Serial articles
- Code structure for reading Zepto source code
- Internal method of reading Zepto source code
- Tool functions for reading Zepto source code
- Reading the Magic of Zepto Source Code$
- Collection operations for reading Zepto source code
- Reading Zepto Source Collection Element Search
- DOM for reading Zepto source code
- Style operations for reading Zepto source code
- Property operations for reading Zepto source code
- Event module reading Zepto source code
- IE Module for Reading Zepto Source Code
- Callbacks module reading Zepto source code
- Deferred Module for Reading Zepto Source Code
- Ajax Module for Reading Zepto Source Code
- Assets module reading Zepto source code
- Selector module for reading Zepto source code
Reference resources
- Source code analysis of zepto touch Library
- PointerEvent
- Pointer events
- TouchEvent
- Touch
- GestureEvent
- MSGestureEvent
- Step by step DIY zepto library, research zepto source code 8-touch module
- zepto source learning - 06 touch
- touch.js of zepto source code
- addPointer method
License
Signature - Noncommercial Use - Prohibition of Deduction 4.0 International (CC BY-NC-ND 4.0)
Finally, all articles will be sent to the Wechat Public Number synchronously. Welcome your attention and comments.
Author: Diagonally on the other side