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, thanclickFaster 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:

  1. Creating a gesture object
  2. Specify the target element
  3. 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

  1. Read the code structure of Zepto source code
  2. Internal method of reading Zepto source code
  3. Read Zepto source tool function
  4. Read Zepto source code magic $
  5. Read Zepto source set operation
  6. Read Zepto source collection element search
  7. Read Zepto source operation DOM
  8. Read Zepto source style operation
  9. Read Zepto source properties operation
  10. Read the Event module of Zepto source code
  11. Read Zepto source IE module
  12. Read the Zepto source Callbacks module
  13. Read the Deferred module of Zepto source code
  14. Read Zepto source Ajax module
  15. Read the Assets module of Zepto source code
  16. 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