成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何實(shí)現(xiàn)swipe、tap、longTap等自定義事件

羅志環(huán) / 3507人閱讀

摘要:分別存儲(chǔ)事件的定時(shí)器。事件定時(shí)器延時(shí)時(shí)間存儲(chǔ)事件對(duì)象滑動(dòng)方向判斷我們根據(jù)下圖以及對(duì)應(yīng)的代碼來(lái)理解滑動(dòng)的時(shí)候方向是如何判定的。取消長(zhǎng)按,以及取消所有事件取消長(zhǎng)按取消所有事件方式都是類似,先調(diào)用取消定時(shí)器,然后釋放對(duì)應(yīng)的變量,等候垃圾回收。

前言
移動(dòng)端原生支持touchstart、touchmove、touchend等事件,但是在平常業(yè)務(wù)中我們經(jīng)常需要使用swipetap、doubleTap、longTap等事件去實(shí)現(xiàn)想要的效果,對(duì)于這種自定義事件他們底層是如何實(shí)現(xiàn)的呢?讓我們從Zepto.jstouch模塊去分析其原理。您也可以直接查看touch.js源碼注釋

源碼倉(cāng)庫(kù)

原文鏈接

事件簡(jiǎn)述
Zepto的touch模塊實(shí)現(xiàn)了很多與手勢(shì)相關(guān)的自定義事件,分別是swipe, swipeLeft, swipeRight, swipeUp, swipeDown,doubleTap, tap, singleTap, longTap
事件名稱 事件描述
swipe 滑動(dòng)事件
swipeLeft ←左滑事件
swipeRight →右滑事件
swipeUp ↑上滑事件
swipeDown ↓下滑事件
doubleTap 雙擊事件
tap 點(diǎn)擊事件(非原生click事件)
singleTap 單擊事件
longTap 長(zhǎng)按事件
;["swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap", "singleTap", "longTap"].forEach(function(eventName){
  $.fn[eventName] = function(callback){ return this.on(eventName, callback) }
})

可以看到Zepto把這些方法都掛載到了原型上,這意味著,你可以直接用簡(jiǎn)寫的方式例如$("body").tap(callback)

前置條件
在開始分析這些事件如何實(shí)現(xiàn)之前,我們先了解一些前置條件

部分內(nèi)部變量

var touch = {},
    touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
    // 長(zhǎng)按事件定時(shí)器時(shí)間
    longTapDelay = 750,
    gesture

touch: 用以存儲(chǔ)手指操作的相關(guān)信息,例如手指按下時(shí)的位置,離開時(shí)的坐標(biāo)等。

touchTimeout,tapTimeout, swipeTimeout,longTapTimeout分別存儲(chǔ)singleTap、tap、swipe、longTap事件的定時(shí)器。

longTapDelay:longTap事件定時(shí)器延時(shí)時(shí)間

gesture: 存儲(chǔ)ieGesture事件對(duì)象

滑動(dòng)方向判斷(swipeDirection)

我們根據(jù)下圖以及對(duì)應(yīng)的代碼來(lái)理解滑動(dòng)的時(shí)候方向是如何判定的。需要注意的是瀏覽器中的“坐標(biāo)系”和數(shù)學(xué)中的坐標(biāo)系還是不太一樣,Y軸有點(diǎn)反過(guò)來(lái)的意思。

/**
  * 判斷移動(dòng)的方向,結(jié)果是Left, Right, Up, Down中的一個(gè)
  * @param  {} x1 起點(diǎn)的橫坐標(biāo)
  * @param  {} x2 終點(diǎn)的橫坐標(biāo)
  * @param  {} y1 起點(diǎn)的縱坐標(biāo)
  * @param  {} y2 終點(diǎn)的縱坐標(biāo)
  */

function swipeDirection(x1, x2, y1, y2) {
  /**
    * 1. 第一個(gè)三元運(yùn)算符得到如果x軸滑動(dòng)的距離比y軸大,那么是左右滑動(dòng),否則是上下滑動(dòng)
    * 2. 如果是左右滑動(dòng),起點(diǎn)比終點(diǎn)大那么往左滑動(dòng)
    * 3. 如果是上下滑動(dòng),起點(diǎn)比終點(diǎn)大那么往上滑動(dòng)
    * 需要注意的是這里的坐標(biāo)和數(shù)學(xué)中的有些不一定 縱坐標(biāo)有點(diǎn)反過(guò)來(lái)的意思
    * 起點(diǎn)p1(1, 0) 終點(diǎn)p2(1, 1)
    */
  return Math.abs(x1 - x2) >=
    Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "Left" : "Right") : (y1 - y2 > 0 ? "Up" : "Down")
}

觸發(fā)長(zhǎng)按事件

function longTap() {
  longTapTimeout = null
  if (touch.last) {
    // 觸發(fā)el元素的longTap事件
    touch.el.trigger("longTap")
    touch = {}
  }
}

在觸發(fā)長(zhǎng)按事件之前先將longTapTimeout定時(shí)器取消,如果touch.last還存在則觸發(fā)之,為什么要判斷touch.last呢,因?yàn)?b>swip, doubleTap,singleTap會(huì)將touch對(duì)象置空,當(dāng)這些事件發(fā)生的時(shí)候,自然不應(yīng)該發(fā)生長(zhǎng)按事件。

取消長(zhǎng)按,以及取消所有事件

// 取消長(zhǎng)按
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 = {}
}

方式都是類似,先調(diào)用clearTimeout取消定時(shí)器,然后釋放對(duì)應(yīng)的變量,等候垃圾回收。

整體結(jié)構(gòu)分析

$(document).ready(function(){
  /**
    * now 當(dāng)前觸摸時(shí)間
    * delta 兩次觸摸的時(shí)間差
    * deltaX x軸變化量
    * deltaY Y軸變化量
    * firstTouch 觸摸點(diǎn)相關(guān)信息
    * _isPointerType 是否是pointerType
    */
  var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType

  $(document)
    .bind("MSGestureEnd", function(e){
      // xxx 先不看這里
    })
    .on("touchstart MSPointerDown pointerdown", function(e){
      // xxx 關(guān)注這里
    })
    .on("touchmove MSPointerMove pointermove", function(e){
      // xxx 關(guān)注這里
    })
    .on("touchend MSPointerUp pointerup", function(e){
      // xxx 關(guān)注這里
    })
    .on("touchcancel MSPointerCancel pointercancel", cancelAll)

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

這里將詳細(xì)代碼暫時(shí)省略了,留出整體框架,可以看出Zepto在dom,ready的時(shí)候在document上添加了MSGestureEnd,touchstart MSPointerDown pointerdown,touchmove MSPointerMove pointermove,touchcancel MSPointerCancel pointercancel等事件,最后還給在window上加了scroll事件。我們將目光聚焦在touchstart,touchmove,touchend對(duì)應(yīng)的邏輯,其他相對(duì)少見的事件在暫不討論

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

要走到touchstart事件處理程序后續(xù)邏輯中,需要先滿足一些條件。到底是哪些條件呢?先來(lái)看看isPointerEventType, isPrimaryTouch兩個(gè)函數(shù)做了些什么。

**isPointerEventType

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

Pointer Event相關(guān)知識(shí)點(diǎn)擊這里

isPrimaryTouch

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

根據(jù)mdn pointerType,其類型可以是mouse,pen,touch,這里只處理其值為touch并且isPrimary為true的情況。

接著回到

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

其實(shí)就是過(guò)濾掉非觸摸事件。

觸摸點(diǎn)信息兼容處理

// 如果是pointerdown事件則firstTouch保存為e,否則是e.touches第一個(gè)
firstTouch = _isPointerType ? e : e.touches[0]

這里只清楚e.touches[0]的處理邏輯,另一種不太明白,望有知曉的同學(xué)告知一下,感謝感謝。

復(fù)原終點(diǎn)坐標(biāo)

// 一般情況下,在touchend或者cancel的時(shí)候,會(huì)將其清除,如果用戶調(diào)阻止了默認(rèn)事件,則有可能清空不了,但是為什么要將終點(diǎn)坐標(biāo)清除呢?
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
}

存儲(chǔ)觸摸點(diǎn)部分信息

// 保存當(dāng)前時(shí)間
now = Date.now()
// 保存兩次點(diǎn)擊時(shí)候的時(shí)間間隔,主要用作雙擊事件
delta = now - (touch.last || now)
// touch.el 保存目標(biāo)節(jié)點(diǎn)
// 不是標(biāo)簽節(jié)點(diǎn)則使用該節(jié)點(diǎn)的父節(jié)點(diǎn),注意有偽元素
touch.el = $("tagName" in firstTouch.target ?
  firstTouch.target : firstTouch.target.parentNode)
// touchTimeout 存在則清除之,可以避免重復(fù)觸發(fā)
touchTimeout && clearTimeout(touchTimeout)
// 記錄起始點(diǎn)坐標(biāo)(x1, y1)(x軸,y軸)
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY

判斷雙擊事件

// 兩次點(diǎn)擊的時(shí)間間隔 > 0 且 < 250 毫秒,則當(dāng)做doubleTap事件處理
if (delta > 0 && delta <= 250) touch.isDoubleTap = true

處理長(zhǎng)按事件

// 將now設(shè)置為touch.last,方便上面可以計(jì)算兩次點(diǎn)擊的時(shí)間差
touch.last = now
// longTapDelay(750毫秒)后觸發(fā)長(zhǎng)按事件
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]
  // 取消長(zhǎng)按事件,都移動(dòng)了,當(dāng)然不是長(zhǎng)按了
  cancelLongTap()
  // 終點(diǎn)坐標(biāo) (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)
})

手指移動(dòng)的時(shí)候,做了三件事情。

取消長(zhǎng)按事件

記錄終點(diǎn)坐標(biāo)

記錄x軸和y軸的移動(dòng)變化量

touchend
.on("touchend MSPointerUp pointerup", function(e){
  if((_isPointerType = isPointerEventType(e, "up")) &&
    !isPrimaryTouch(e)) return
  // 取消長(zhǎng)按事件  
  cancelLongTap()
  // 滑動(dòng)事件,只要X軸或者Y軸的起始點(diǎn)和終點(diǎn)的距離超過(guò)30則認(rèn)為是滑動(dòng),并觸發(fā)滑動(dòng)(swip)事件,
  // 緊接著馬上觸發(fā)對(duì)應(yīng)方向的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對(duì)象的last屬性,在touchstart事件中添加,所以觸發(fā)了start事件便會(huì)存在  
  // 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
    // 只有當(dāng)X軸和Y軸的變化量都小于30的時(shí)候,才認(rèn)為有可能觸發(fā)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)
        // 創(chuàng)建自定義事件
        var event = $.Event("tap")
        // 往自定義事件中添加cancelTouch回調(diào)函數(shù),這樣使用者可以通過(guò)該方法取消所有的事件
        event.cancelTouch = cancelAll
        // [by paper] fix -> "TypeError: "undefined" is not an object (evaluating "touch.el.trigger"), when double tap
        // 當(dāng)目標(biāo)元素存在,觸發(fā)tap自定義事件
        if (touch.el) touch.el.trigger(event)

        // trigger double tap immediately
        // 如果是doubleTap事件,則觸發(fā)之,并清除touch
        if (touch.isDoubleTap) {
          if (touch.el) touch.el.trigger("doubleTap")
          touch = {}
        }

        // trigger single tap after 250ms of inactivity
        // 否則在250毫秒之后。觸發(fā)單擊事件
        else {
          touchTimeout = setTimeout(function(){
            touchTimeout = null
            if (touch.el) touch.el.trigger("singleTap")
            touch = {}
          }, 250)
        }
      }, 0)
    } else {
      // 不是tap相關(guān)的事件
      touch = {}
    }
    // 最后將變化量信息清空
    deltaX = deltaY = 0

})

touchend事件觸發(fā)時(shí),相應(yīng)的注釋都在上面了,但是我們來(lái)分解一下這段代碼。

swip事件相關(guān)

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)

手指離開后,通過(guò)判斷x軸或者y軸的位移,只要其中一個(gè)跨度大于30便會(huì)觸發(fā)swip及其對(duì)應(yīng)方向的事件。

tap,doubleTap,singleTap

這三個(gè)事件可能觸發(fā)的前提條件是touch對(duì)象中還存在last屬性,從touchstart事件處理程序中知道last在其中記錄,而在touchend之前被清除的時(shí)機(jī)是長(zhǎng)按事件被觸發(fā)longTap,取消所有事件被調(diào)用cancelAll

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

只有當(dāng)x軸和y軸的變化量都小于30的時(shí)候才會(huì)觸發(fā)tap事件,注意在觸發(fā)tap事件之前,Zepto還將往事件對(duì)象上添加了cancelTouch屬性,對(duì)應(yīng)的也就是cancelAll方法,即你可以通過(guò)他取消所有的touch相關(guān)事件。

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

在發(fā)生觸發(fā)tap事件之后,如果是doubleTap,則會(huì)緊接著觸發(fā)doubleTap事件,否則250毫秒之后觸發(fā)singleTap事件,并且都會(huì)講touch對(duì)象置為空對(duì)象,以便下次使用

// 最后將變化量信息清空
deltaX = deltaY = 0
touchcancel
.on("touchcancel MSPointerCancel pointercancel", cancelAll)

當(dāng)touchcancel被觸發(fā)的時(shí)候,取消所有的事件。

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

當(dāng)滾動(dòng)事件被觸發(fā)的時(shí)候,取消所有的事件(這里有些不解,滾動(dòng)事件觸發(fā),完全有可能是要觸發(fā)tap或者swip等事件?。?。

結(jié)尾
最后說(shuō)一個(gè)面試中經(jīng)常會(huì)問(wèn)的問(wèn)題,touch擊穿現(xiàn)象。如果對(duì)此有興趣可以查看移動(dòng)端click延遲及zepto的穿透現(xiàn)象, [新年第一發(fā)--深入不淺出zepto的Tap擊穿問(wèn)題
](https://zhuanlan.zhihu.com/p/...
參考

移動(dòng)端click延遲及zepto的穿透現(xiàn)象

[新年第一發(fā)--深入不淺出zepto的Tap擊穿問(wèn)題

](https://zhuanlan.zhihu.com/p/...

讀Zepto源碼之Touch模塊

pointerType

[[翻譯]整合鼠標(biāo)、觸摸 和觸控筆事件的Html5 Pointer Event Api](https://juejin.im/post/594e06...

文章目錄

touch.js

如何實(shí)現(xiàn)swipe、tap、longTap等自定義事件 (2017-12-22)

ie.js

Zepto源碼分析之ie模塊(2017-11-03)

data.js

Zepto中數(shù)據(jù)緩存原理與實(shí)現(xiàn)(2017-10-03)

form.js

Zepto源碼分析之form模塊(2017-10-01)

zepto.js

這些Zepto中實(shí)用的方法集(2017-08-26)

Zepto核心模塊之工具方法拾遺 (2017-08-30)

看Zepto如何實(shí)現(xiàn)增刪改查DOM (2017-10-2)

Zepto這樣操作元素屬性(2017-11-13)

向Zepto學(xué)習(xí)關(guān)于"偏移"的那些事(2017-12-10)

event.js

mouseenter與mouseover為何這般糾纏不清?(2017-06-05)

向Zepto.js學(xué)習(xí)如何手動(dòng)觸發(fā)DOM事件(2017-06-07)

誰(shuí)說(shuō)你只是"會(huì)用"jQuery?(2017-06-08)

ajax.js

原來(lái)你是這樣的jsonp(原理與具體實(shí)現(xiàn)細(xì)節(jié))(2017-06-11)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93115.html

相關(guān)文章

  • 讀Zepto源碼之Touch模塊

    摘要:在觸發(fā)事件前,先將保存定時(shí)器的變量釋放,如果對(duì)象中存在,則觸發(fā)事件,保存的是最后觸摸的時(shí)間。如果有觸發(fā)的定時(shí)器,清除定時(shí)器即可阻止事件的觸發(fā)。其實(shí)就是清除所有相關(guān)的定時(shí)器,最后將對(duì)象設(shè)置為。進(jìn)入時(shí),立刻清除定時(shí)器的執(zhí)行。 大家都知道,因?yàn)闅v史原因,移動(dòng)端上的點(diǎn)擊事件會(huì)有 300ms 左右的延遲,Zepto 的 touch 模塊解決的就是移動(dòng)端點(diǎn)擊延遲的問(wèn)題,同時(shí)也提供了滑動(dòng)的 swip...

    Prasanta 評(píng)論0 收藏0
  • 微信小程序 自制手勢(shì)庫(kù)

    摘要:微信小程序手勢(shì)事件庫(kù)由于微信小程序只能夠支持時(shí)間,對(duì)于比較復(fù)雜的事件只能自己實(shí)現(xiàn)因此自己對(duì)庫(kù)進(jìn)行了改造,開發(fā)了時(shí)候微信小程序手勢(shì)事件庫(kù)使用進(jìn)行編寫手勢(shì)庫(kù)支持以下事件倉(cāng)庫(kù)地址點(diǎn)我點(diǎn)我使用由于和微信小程序強(qiáng)綁定,因此需要在元素上面綁定好所有的事 WxTouchEvent 微信小程序手勢(shì)事件庫(kù) 由于微信小程序只能夠支持 tap,longtap,touchstart,touchmove,tou...

    cucumber 評(píng)論0 收藏0
  • mTouch移動(dòng)端 ( 兼容pc端) 手勢(shì)操作庫(kù)

    摘要:移動(dòng)端兼容端手勢(shì)操作庫(kù),支持的事件單擊雙擊長(zhǎng)按滑動(dòng)開始滑動(dòng)結(jié)束滑動(dòng)向左劃向右劃向上劃向下劃提供的接口配置項(xiàng)單擊事件允許的滑動(dòng)距離雙擊事件的延時(shí)時(shí)長(zhǎng)兩次單擊的最大時(shí)間間隔長(zhǎng)按事件的最小時(shí)長(zhǎng)觸發(fā)方向滑動(dòng)的最小距離觸發(fā)方向滑動(dòng)允許的最長(zhǎng)時(shí)長(zhǎng)以上是 mTouch mTouch移動(dòng)端 ( 兼容pc端) 手勢(shì)操作庫(kù),view on github 支持的事件: tap 單擊 doubletap ...

    dadong 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<