How to swipe, tap, longTap and other custom events

Foreword

Mobile end native support for touchstart, touchmove, touchendand so on, but in ordinary business, we often need to use the swipe, tap, doubleTap, longTapand other events to achieve the desired effect, for this custom events underlying them is how to achieve it? Let Zepto.js's touchmodule to analyze its principles. You can also view directly touch.js source code comments

Source Repository

Description link

touch

? Event Brief

Zepto's touch module implements a number associated with the gesture of custom events, namely swipe, swipeLeft, swipeRight, swipeUp, swipeDown, doubleTap, tap, singleTap,longTap

Event Name Event Description
swipe Slip events
swipeLeft ← left slip events
swipeRight → right slide event
swipeUp Slip events on ↑
swipeDown ↓ decline events
doubleTap Double-click the event
tap Click on the event (click event of non-native)
singleTap Click event
longTap Press events
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
  $.fn[eventName] = function(callback){ return this.on(eventName, callback) }
})

Zepto can see these methods to mount on the prototype, which means that you can directly use a shorthand way of example,$('body').tap(callback)

Pre-conditions

Before beginning the analysis of how to achieve these events, we first understand some of the pre-conditions

  • Part of the internal variables
var touch = {},
    touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
    // 长按事件定时器时间
    longTapDelay = 750,
    gesture

touch: Finger-operated to store information, such as the position of the finger when pressed, when the coordinates of departure.

touchTimeout, tapTimeout, swipeTimeout, longTapTimeoutStore singleTap, tap, swipe, timer longTap events.

longTapDelay:? LongTap event timer delay time

gesture: Storage ieGesture event object

  • Analyzing the sliding direction (swipeDirection)

We according to the following map and the corresponding time code to understand how the sliding direction is determined. Note that the browser "coordinate" and mathematics is not the same coordinate system, Y-axis turn a bit mean.

Phone screen plot

/**
  * 判断移动的方向,结果是Left, Right, Up, Down中的一个
  * @param  {} x1 起点的横坐标
  * @param  {} x2 终点的横坐标
  * @param  {} y1 起点的纵坐标
  * @param  {} y2 终点的纵坐标
  */

function swipeDirection(x1, x2, y1, y2) {
  /**
    * 1. 第一个三元运算符得到如果x轴滑动的距离比y轴大,那么是左右滑动,否则是上下滑动
    * 2. 如果是左右滑动,起点比终点大那么往左滑动
    * 3. 如果是上下滑动,起点比终点大那么往上滑动
    * 需要注意的是这里的坐标和数学中的有些不一定 纵坐标有点反过来的意思
    * 起点p1(1, 0) 终点p2(1, 1)
    */
  return Math.abs(x1 - x2) >=
    Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
}

  • Press the trigger event
function longTap() {
  longTapTimeout = null
  if (touch.last) {
    // 触发el元素的longTap事件
    touch.el.trigger('longTap')
    touch = {}
  }
}

First before triggering long press event longTapTimeouttimer canceled if touch.lastthere is a trigger, the judge why should touch.lastit, because swip, doubleTap, singleTapwill touch the object blank, when these events? Nature should not be a long press event.

  • Cancel the press, as well as cancel all events
// 取消长按
function cancelLongTap() {
  if (longTapTimeout) clearTimeout(longTapTimeout)
  longTapTimeout = null
}

// 取消所有事件

function cancelAll() {
  if (touchTimeout) clearTimeout(touchTimeout)
  if (tapTimeout) clearTimeout(tapTimeout)
  if (swipeTimeout) clearTimeout(swipeTimeout)
  if (longTapTimeout) clearTimeout(longTapTimeout)
  touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
  touch = {}
}

Ways that are similar to the first call clearTimeout cancel the timer, then release the corresponding variable, waiting for garbage collection.

Overall Structural Analysis



$(document).ready(function(){
  /**
    * now 当前触摸时间
    * delta 两次触摸的时间差
    * deltaX x轴变化量
    * deltaY Y轴变化量
    * firstTouch 触摸点相关信息
    * _isPointerType 是否是pointerType
    */
  var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType

  $(document)
    .bind('MSGestureEnd', function(e){
      // xxx 先不看这里
    })
    .on('touchstart MSPointerDown pointerdown', function(e){
      // xxx 关注这里
    })
    .on('touchmove MSPointerMove pointermove', function(e){
      // xxx 关注这里
    })
    .on('touchend MSPointerUp pointerup', function(e){
      // xxx 关注这里
    })
    .on('touchcancel MSPointerCancel pointercancel', cancelAll)

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


Here the code will be detailed temporarily omitted, leaving the overall framework, can be seen in Zepto? Dom, ready at the time documentthe addition of MSGestureEnd, touchstart MSPointerDown pointerdown, touchmove MSPointerMove pointermove, touchcancel MSPointerCancel pointercanceland so on, and finally back to the window added in scrollthe event. We will be focusing its attention on touchstart, touchmove, touchendthe corresponding logic, other relatively rare event will not discuss

touchstart

if((_isPointerType = isPointerEventType(e, 'down')) 
&& !isPrimaryTouch(e)) return

To go to touchstartthe event handler follow logic, we need to meet certain conditions. In the end what conditions? Let's look at isPointerEventType, isPrimaryTouchthe two functions done.

**isPointerEventType

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

Pointer Event Knowledge Click here

isPrimaryTouch

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

The MDN pointerType , which type can be mouse, pen, touch, which is here only the processing and touch isPrimary is true? Situation.

Then back

if((_isPointerType = isPointerEventType(e, 'down')) 
&& !isPrimaryTouch(e)) return

In fact, filtering out non-touch event.

Touch point information processing compatible

// 如果是pointerdown事件则firstTouch保存为e,否则是e.touches第一个
firstTouch = _isPointerType ? e : e.touches[0]

Here only clear e.touches[0]processing logic, another not quite understand, hope to know the students informed about, thanks thanks.

End coordinates recovery

// 一般情况下,在touchend或者cancel的时候,会将其清除,如果用户调阻止了默认事件,则有可能清空不了,但是为什么要将终点坐标清除呢?
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
}

Storing touch point information section

// 保存当前时间
now = Date.now()
// 保存两次点击时候的时间间隔,主要用作双击事件
delta = now - (touch.last || now)
// touch.el 保存目标节点
// 不是标签节点则使用该节点的父节点,注意有伪元素
touch.el = $('tagName' in firstTouch.target ?
  firstTouch.target : firstTouch.target.parentNode)
// touchTimeout 存在则清除之,可以避免重复触发
touchTimeout && clearTimeout(touchTimeout)
// 记录起始点坐标(x1, y1)(x轴,y轴)
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY

Double-click the event to determine

// 两次点击的时间间隔 > 0 且 < 250 毫秒,则当做doubleTap事件处理
if (delta > 0 && delta <= 250) touch.isDoubleTap = true

Long press event processing

// 将now设置为touch.last,方便上面可以计算两次点击的时间差
touch.last = now
// longTapDelay(750毫秒)后触发长按事件
longTapTimeout = setTimeout(longTap, longTapDelay)

touchmove

.on('touchmove MSPointerMove pointermove', function(e){
  if((_isPointerType = isPointerEventType(e, 'move')) &&
    !isPrimaryTouch(e)) return
  firstTouch = _isPointerType ? e : e.touches[0]
  // 取消长按事件,都移动了,当然不是长按了
  cancelLongTap()
  // 终点坐标 (x2, y2)
  touch.x2 = firstTouch.pageX
  touch.y2 = firstTouch.pageY
  // 分别记录X轴和Y轴的变化量
  deltaX += Math.abs(touch.x1 - touch.x2)
  deltaY += Math.abs(touch.y1 - touch.y2)
})

Finger movement when do three things.

  1. Cancel Press Event
  2. Record end coordinates
  3. Movement variation amount of the recording of the x-axis and y-axis

touchEnd

.on('touchend MSPointerUp pointerup', function(e){
  if((_isPointerType = isPointerEventType(e, 'up')) &&
    !isPrimaryTouch(e)) return
  // 取消长按事件  
  cancelLongTap()
  // 滑动事件,只要X轴或者Y轴的起始点和终点的距离超过30则认为是滑动,并触发滑动(swip)事件,
  // 紧接着马上触发对应方向的swip事件(swipLeft, swipRight, swipUp, swipDown)
  // swipe
  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)
  // touch对象的last属性,在touchstart事件中添加,所以触发了start事件便会存在  
  // normal tap
  else if ('last' in touch)
    // don't fire tap when delta position changed by more than 30 pixels,
    // for instance when moving to a point and back to origin
    // 只有当X轴和Y轴的变化量都小于30的时候,才认为有可能触发tap事件
    if (deltaX < 30 && deltaY < 30) {
      // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
      // ('tap' fires before 'scroll')
      tapTimeout = setTimeout(function() {

        // trigger universal 'tap' with the option to cancelTouch()
        // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
        // 创建自定义事件
        var event = $.Event('tap')
        // 往自定义事件中添加cancelTouch回调函数,这样使用者可以通过该方法取消所有的事件
        event.cancelTouch = cancelAll
        // [by paper] fix -> "TypeError: 'undefined' is not an object (evaluating 'touch.el.trigger'), when double tap
        // 当目标元素存在,触发tap自定义事件
        if (touch.el) touch.el.trigger(event)

        // trigger double tap immediately
        // 如果是doubleTap事件,则触发之,并清除touch
        if (touch.isDoubleTap) {
          if (touch.el) touch.el.trigger('doubleTap')
          touch = {}
        }

        // trigger single tap after 250ms of inactivity
        // 否则在250毫秒之后。触发单击事件
        else {
          touchTimeout = setTimeout(function(){
            touchTimeout = null
            if (touch.el) touch.el.trigger('singleTap')
            touch = {}
          }, 250)
        }
      }, 0)
    } else {
      // 不是tap相关的事件
      touch = {}
    }
    // 最后将变化量信息清空
    deltaX = deltaY = 0

})

When touchend event is triggered, the corresponding annotations are in it, but let's break this code.

swip-related events

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)

After leaving the finger, the displacement is determined by the x-axis or y-axis, as long as a span of greater than 30 is triggered swip? Event and the corresponding direction.

tap,doubleTap,singleTap

Prerequisites these three events may be triggered by touch objects there last property, know where the last record from touchstart event handler, and before touchend cleared time is long press event is triggered longTap, canceled all events is calledcancelAll

if (deltaX < 30 && deltaY < 30) {
  // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
  // ('tap' fires before 'scroll')
  tapTimeout = setTimeout(function() {

    // trigger universal 'tap' with the option to cancelTouch()
    // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
    var event = $.Event('tap')
    event.cancelTouch = cancelAll
    // [by paper] fix -> "TypeError: 'undefined' is not an object (evaluating 'touch.el.trigger'), when double tap
    if (touch.el) touch.el.trigger(event)
  }    
}

Only when the amount of change in the x-axis and y-axis are less than 30 will trigger tapan event, tap attention before triggering event, Zepto also added to the event object cancelTouch property is cancelAll corresponding method that you can he canceled all touch-related events.


// trigger double tap immediately

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

// trigger single tap after 250ms of inactivity

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


After triggering tap the incident, if it is doubleTap, it will immediately trigger doubleTap event, otherwise the event is triggered singleTap after 250 milliseconds, and will speak touch objects as null object for the next use

// 最后将变化量信息清空
deltaX = deltaY = 0

touchcancel


.on('touchcancel MSPointerCancel pointercancel', cancelAll)

When touchcancelthe time is triggered, cancel all events.

scroll


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

When the rolling event is triggered, cancel all events (here somewhat puzzled, scroll event trigger, it is very possible to trigger events such as tap or swip ah).

end

Finally, he said in a interview frequently asked questions, touch breakdown. If you are interested you can view the penetration phenomenon mobile end delay and click the zepto , New Year's hair - not a shallow depth of Tap out zepto breakdown problems

reference

  1. Mobile end delay and click zepto penetration phenomenon

  2. New Year's hair - not a shallow depth of Tap out zepto breakdown problems

  3. Zepto read the source code of the module Touch

  4. pointerType

  5. [Translation] integrated mouse, touch and stylus events Html5 Pointer Event Api

Article Directory

Guess you like

Origin www.cnblogs.com/jlfw/p/11794757.html