Read the touch module of zepto source code

Time:2020-1-14

As we all know, for historical reasons, the click events on the mobile terminal will have300msLeft right delay,ZeptoOftouchThe module solves the problem of mobile terminal click delay and provides slidingswipeEvent.

Reading the zepto source series has been put on GitHub. Welcome to star: reading zepto

Source version

The source code of this article is zepto1.2.0

GitBook

《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) }
})

As you can see from the above code,ZeptoThe following events are implemented:

  • Swipe: slide event
  • Swipeleft: slide event left
  • Swipiright: swipe event right
  • Swipeup: slide event up
  • Swipededown: slide down event
  • Doubletap: screen double click event
  • Tap: screen click eventclickFaster event response
  • Single tap: screen click event
  • Longtap: long press event

And registered shortcuts 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')
}

The return is a sliding method.

x1byX axisStarting point coordinates,x2byX axisEnd point coordinates,y1byY axisStarting point coordinates,y2byY axisEnd point coordinates.

There are multiple sets of ternary expressions here. The first comparison isX axisandY axisSlide distance on, ifX axisSliding distance ratio ofY axisIf it is large, it means sliding left and right, otherwise it means sliding up and down.

stayX axisUp, if the start position is larger than the end position, it means to slide to the left and returnLeft, otherwise, slide right, returnRight

stayY axisUp, if the starting position is larger than the ending position, it means sliding upward and returningUp, otherwise, slide down and returnDown

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.

touchObjects store information during touch.

TriggerlongTapBefore the event, the variables of the timer will be savedlongTapTimeoutRelease, iftouchObject exists inlastTriggerlongTapEvent,lastSave the last touch time. Finally willtouchReset to an empty object for next use.

cancelLongTap

function cancelLongTap() {
  if (longTapTimeout) clearTimeout(longTapTimeout)
  longTapTimeout = null
}

RevokelongTapEvent.

If there is a triggerlongTapClear the timer to preventlongTapEvent.

Finally, we also need tolongTapTimeoutVariables are set tonull, waiting 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 = {}
}

Clears the execution of all events.

In fact, it is to clear all relevant timers, and finallytouchObject set tonull

isPrimaryTouch

function isPrimaryTouch(event){
  return (event.pointerType == 'touch' ||
          event.pointerType == event.MSPOINTER_TYPE_TOUCH)
  && event.isPrimary
}

Whether it is the main contact.

WhenpointerTypebytouchalsoisPrimarybytrueWhen, it is the main contact.pointerTypeMay betouchpenandmouse, which only deals with finger touch.

isPointerEventType

function isPointerEventType(e, type){
  return (e.type == 'pointer'+type ||
          e.type.toLowerCase() == 'mspointer'+type)
}

Triggered bypointerEvent

In the lower version of mobile IE browser, onlyPointerEvent, not implementedTouchEvent, so we need this to judge.

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)

Let’s start with a few variables,nowUsed to save the current time,deltaIt is used to save the time difference between two touch,deltaXFor preservationX axisDisplacement on,deltaYFor preservationY axisDisplacement on,firstTouchSave the information of the initial touch point,_isPointerTypeSave aspointerEventThe result of judgment.

As you can see from above,ZeptoThe event triggered is from thetouchpointerOr IE’sguestureIn the event, it is calculated according to different situations. These events are bound to thedocumentUp.

Handling of IE gesture events

IEThere are three steps to use the gesture:

  1. Create gesture object
  2. Specify target element
  3. Specifies the pointer to be processed in 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 stepaddPointerMethod that specifies 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)
  }
})

And then there’s gesture analysis,GestureOnly deal with insideswipeEvent.

velocityXandvelocityYRespectivelyX axisandY axisRate on. Here to1or-1Critical point, judgmentswipeDirection.

IfswipeIf the direction of is present, triggerswipeEvent, but also triggers theswipeEvent.

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]

Here will also beisPointerEventTypeThe result of our judgment is saved to_isPointerTypeUsed to determine whether it isPointerEvent

The judgment here is actually only dealing withPointerEventandTouchEventAndTouchEventOfisPrimaryMust betrue

becauseTouchEventMulti touch is supported. Only the first touch is saved herefirstTouchVariables.

Reset end coordinates

if (e.touches && e.touches.length === 1 && touch.x2) {
  touch.x2 = undefined
  touch.y2 = undefined
}

If records are needed, the end coordinates need to be updated.

Under normal circumstances,touchThe object will betouchEndperhapscancelIf the user calls thepreventDefaultWait, you may not be empty.

I don’t understand why it’s only intouchesWhen a single point of operation is used, how about clearing it? Don’t you need to empty multiple touch points?

Record touch point 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

nowUsed to save the current time.

deltaIt is used to save the time interval between two clicks and handle double click events.

touch.elTo save the target element, here is a judgment, iftargetWhen it is not a label node, the parent node is taken as the target element. This will appear when the pseudo class element is clicked.

IftouchTimeoutIf yes, clear the timer to avoid repeated triggering.

touch.x1andtouch.y1Save separatelyX axisCoordinate sumY axisCoordinates.

Double click event

if (delta > 0 && delta <= 250) touch.isDoubleTap = true

You can see it very clearly,ZeptoThe time interval between two clicks is less than250msAct asdoubleTapEvent handling, willisDoubleTapSet totrue

Long press event

touch.last = now
longTapTimeout = setTimeout(longTap, longTapDelay)

taketouch.lastSet 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 be in the750msPost trigger.

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)
})

moveThe event handles two things, one is to record the coordinates of the end point, the other is to calculate the displacement between the start point and the end point.

Note that there are also callscancelLongTapThe long press timer is cleared to avoid triggering the long press event. Because of the movement, it’s certainly 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)

Get intoendRemove immediatelylongTapTimer execution.

As you can see, the distance between the start point and the end point exceeds30Will be judged asswipeSlide events.

After triggeringswipeAfter the event, immediately trigger theswipeEvent.

Be careful,swipeThe event is not inendWhen a series of events are triggered, a0msWhat’s the use of this timer to trigger events asynchronously? 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, I see the point. First of all, judgelastWhether it exists, fromstartAs you can see in, if triggeredstartlastThere must be, but if a long press event is triggered,touchThe object will be cleared and will not be triggered againtapEvent.

If notswipeEvent, nolastThen onlytouchClear, no events are triggered.

At the end of the meetingdeltaXanddeltaYReset to0

triggertapWhen an event occurs, theeventMedium pluscancelTouchMethod, through which the outside world can cancel the execution of all events.

It’s the same heresetTimeoutAsynchronous trigger event.

doubleTap

if (touch.isDoubleTap) {
  if (touch.el) touch.el.trigger('doubleTap')
  touch = {}
}

thisisDoubleTapstaystartIt’s confirmed by. It’s been analyzed above. In theendTrigger whendoubleTapEvent.

Therefore, it can be known that thedoubleTapEvent triggered twice beforetapEvent.

singleTap

touchTimeout = setTimeout(function(){
  touchTimeout = null
  if (touch.el) touch.el.trigger('singleTap')
  touch = {}
}, 250)

If notdoubleTapWill be intapEvent triggered250msAfter triggersingleTapEvent.

cancel

.on('touchcancel MSPointerCancel pointercancel', cancelAll)

Be acceptedcancelEvent, callcancelAllMethod to cancel triggering of all events.

scroll

$(window).on('scroll', cancelAll)

From the previous analysis, we can see that all event triggers are asynchronous.

Because inscrollWhen it is triggered asynchronously, it must only respond to rolling eventsscrollProcess and external calls ofcancelTouchMethod, you can cancel the event.

Serial articles

  1. Reading the code structure of zepto source code
  2. Internal method of reading zepto source code
  3. Read the tool function of zepto source code
  4. The magic of reading zepto source code$
  5. Set operation of reading zepto source code
  6. Reading the set element of zepto source code
  7. Read the operation dom of zepto source code
  8. Reading zepto source code style operation
  9. Read the property operation of zepto source code
  10. Read the event module of zepto source code
  11. Read the IE module of zepto source code
  12. Reading the callback module of zepto source code
  13. Reading the deferred module of zepto source code
  14. Reading the Ajax module of zepto source code
  15. Read the assets module of zepto source code
  16. Reading the selector module of 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 8 — touch module
  • Zepto source code learning-06 touch
  • Touch.js of zepto source code
  • addPointer method.aspx)

License

Signature – non-commercial use – deduction prohibited 4.0 International (CC by-nc-nd 4.0)

Finally, all articles will be sent to the WeChat public address synchronously.Read the touch module of zepto source code

Author: opposite corner