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

資訊專欄INFORMATION COLUMN

Hammer.js源碼簡(jiǎn)析

lushan / 1778人閱讀

摘要:最后一點(diǎn)思考都是在冒泡階段綁定事件處理器,為什么不在捕獲階段攔截事件尼,如果一個(gè)向右活動(dòng)的手勢(shì)被識(shí)別,后續(xù)的事件如已經(jīng)沒必要再傳給子節(jié)點(diǎn),完全可以在攔截的元素上處理,這樣性能上也應(yīng)該會(huì)有一點(diǎn)提升,挖個(gè)坑給自己以后實(shí)現(xiàn)一下。

開始

話說(shuō)上周周末閑的蛋疼,突然想了解一下前端手勢(shì)如何處理,好解開自己一個(gè)知識(shí)盲點(diǎn),于是開始啃源碼。。。并紀(jì)錄一下。

一個(gè)手勢(shì)

在我們的前端頁(yè)面里面復(fù)雜的手勢(shì)應(yīng)該是不多見的,一般常用就是拖拉,雙擊,放大縮小這幾個(gè),但是合理運(yùn)用手勢(shì)很明顯也能為我們頁(yè)面的交互體驗(yàn)有一點(diǎn)增色,那么問(wèn)題來(lái)了,如何識(shí)別一個(gè)手勢(shì)尼?

Hammer.js

Hammer.js 應(yīng)該算是前端使用的比較廣泛的一個(gè)手勢(shì)框架了(我所了解的還有一個(gè)AlloyTouch,更小,當(dāng)然它提供的抽象程度是不如Hammer.js的),今天就拿這個(gè)框架來(lái)開刀吧。

配置參數(shù)

我們先來(lái)看Hammer.js的配置參數(shù):

{
      //手勢(shì)事件觸發(fā)時(shí),是否同時(shí)觸發(fā)對(duì)應(yīng)的一個(gè)自定義的dom事件,當(dāng)然這個(gè)沒有直接綁定事件回調(diào)高效
      domEvents: false, 
      //這個(gè)會(huì)影響對(duì)應(yīng)的css屬性touch-action的值,下面會(huì)接著說(shuō)
      touchAction: TOUCH_ACTION_COMPUTE, 
      enable: true, //是否開啟手勢(shì)識(shí)別
      //可以指定在其他的元素上來(lái)檢測(cè)與touch相關(guān)的事件并作為輸入源,如果沒設(shè)置就是當(dāng)前檢測(cè)的元素了
      inputTarget: null, 
      inputClass: null, //輸入源類型,鼠標(biāo)還是觸摸或者是混合
      recognizers: [], //我們配置的手勢(shì)識(shí)別器
      //預(yù)設(shè)的一些手勢(shì)識(shí)別器,格式:[RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]]
      preset: [ 
          [RotateRecognizer, { enable: false }],
          [PinchRecognizer, { enable: false }, ["rotate"]],
          [SwipeRecognizer, { direction: DIRECTION_HORIZONTAL }],
          [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ["swipe"]],
          [TapRecognizer],
          [TapRecognizer, { event: "doubletap", taps: 2 }, ["tap"]],
          [PressRecognizer]
      ],
      cssProps: { //額外的一些css屬性
        userSelect: "none",
        touchSelect: "none",
        touchCallout: "none",
        contentZooming: "none",
        userDrag: "none",
        tapHighlightColor: "rgba(0,0,0,0)"
     }
}

總的來(lái)說(shuō)配置參數(shù)不多,也不算復(fù)雜,這個(gè)框架基本也算是開箱即用了,好,我們接著再深入一點(diǎn)。

初始化

接著來(lái)到源碼里面manager.js,可以看到以下一段的代碼:

export default class Manager {
    constructor() {
        ...
        this.element = element;
        this.input = createInputInstance(this);// 1
        this.touchAction = new TouchAction(this,this.options.touchAction);// 2

        toggleCssProps(this, true);
        
        each(this.options.recognizers, (item) => { //3
           let recognizer = this.add(new (item[0])(item[1]));
               item[2] && recognizer.recognizeWith(item[2]);
               item[3] && recognizer.requireFailure(item[3]);
           }, this);
        }
    ...
} 

1.新建一個(gè)輸入源
根據(jù)設(shè)備的不同手勢(shì)可能是來(lái)自鼠標(biāo)也有可能來(lái)自手機(jī)上的觸摸屏,而且mouse event的屬性和touch event的屬性是有一絲差異的(還有pointer event),所以為了方便后續(xù)處理,Hammer.js也分別定義了不同類型輸入源:MouseInput,PointerEventInput,SingleTouchInput,TouchInput和TouchMouseInput;并針對(duì)不同的事件,對(duì)參數(shù)做了一個(gè)簡(jiǎn)單處理(handler方法),最終得到統(tǒng)一格式的數(shù)據(jù)輸出,就像這樣:

    {
       pointers: touches[0],
       changedPointers: touches[1],
       pointerType: INPUT_TYPE_TOUCH,
       srcEvent: ev
    }

在獲取統(tǒng)一格式的輸入數(shù)據(jù)后,會(huì)交由InputHandler進(jìn)一步處理,會(huì)判斷這次輸入是手勢(shì)的開始還是結(jié)束,如果是開始就會(huì)新建一個(gè)手勢(shì)識(shí)別的session,并且計(jì)算一些與手勢(shì)相關(guān)的數(shù)據(jù)(角度,偏移距離,移動(dòng)方向等),具體可以在compute-input-data.js里面看到。
經(jīng)過(guò)以這一輪計(jì)算,我們已經(jīng)有足夠的數(shù)據(jù)來(lái)支持之后的手勢(shì)識(shí)別了。
另外一提的是,這五種輸入源都繼承了Input,在Input里面事件是這樣綁定的:

    this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
    this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
    this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);

有三種綁定目標(biāo),當(dāng)前的element,inputTarget,element所屬的window,在window上綁定事件處理器還是很必要的(例如拖拉一個(gè)元素的時(shí)候);另外翻了一下代碼,inputTarget綁定都是touch相關(guān)的事件,不是很明白它的意圖和場(chǎng)景,為什么要分離一個(gè)目標(biāo)多帶帶處理觸摸事件。

2.設(shè)置元素樣式里touch-action的值
在手機(jī)瀏覽器里面,一般也會(huì)自帶一些手勢(shì)處理,例如向右滑動(dòng)或者向左滑動(dòng)就是前進(jìn)和后退,所以除了我們自己定義手勢(shì),還需要對(duì)瀏覽器的手勢(shì)做一些限制或者禁止。
這里也舉個(gè)栗子吧,在Hammer.js里面默認(rèn)提供拖拉手勢(shì)的識(shí)別器(就是pan.js),當(dāng)在檢測(cè)水平方向的拖拉的時(shí)候,這個(gè)識(shí)別器會(huì)把touch-action的值設(shè)為pay-y(允許瀏覽器處理垂直方向的拖拉,可以是一個(gè)垂直的滾動(dòng)或者其他),那如果我又接著定義一個(gè)垂直方向拖拉的識(shí)別器時(shí),touch-action的值是多少尼?(答案就是none,瀏覽器不會(huì)幫我們?cè)偬幚砹?,垂直方向滾動(dòng)也只能靠自己),那是怎樣計(jì)算出來(lái)的尼?

在創(chuàng)建TouchAction對(duì)象時(shí),如果配置參數(shù)中touchAction的值為TOUCH_ACTION_COMPUTE,便調(diào)用compute方法開始遍歷recognizers,收集它們所希望設(shè)置的touch-action的值:

    compute() {
        let actions = [];
        each(this.manager.recognizers, (recognizer) => {
          if (boolOrFn(recognizer.options.enable, [recognizer])) {
            actions = actions.concat(recognizer.getTouchAction());
          }
        });
        return cleanTouchActions(actions.join(" "));
      }

最終在cleanTouchActions方法集中計(jì)算最終的值:

     ...
     let hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
     let hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
     if (hasPanX && hasPanY) {
       return TOUCH_ACTION_NONE;
     }
     ...

3.配置手勢(shì)識(shí)別器
主要是配置各個(gè)手勢(shì)識(shí)別器之間的關(guān)系,是否可以協(xié)同還是互斥,用官網(wǎng)一個(gè)例子:

    var hammer = new Hammer(el, {});
    
    var singleTap = new Hammer.Tap({ event: "singletap" });
    var doubleTap = new Hammer.Tap({event: "doubletap", taps: 2 });
    var tripleTap = new Hammer.Tap({event: "tripletap", taps: 3 });
    
    hammer.add([doubleTap, doubleTap, singleTap]);
    
    tripleTap.recognizeWith([doubleTap, singleTap]);
    doubleTap.recognizeWith(singleTap);
    
    doubleTap.requireFailure(tripleTap);
    singleTap.requireFailure([tripleTap, doubleTap]);

以上定義了三個(gè)手勢(shì)識(shí)別器:singleTap,doubleTap和tripleTap,很明顯這個(gè)三個(gè)識(shí)別器是互斥的,如果用戶點(diǎn)三下屏幕時(shí)都觸發(fā)就比較尷尬了;
這里得注意添加的順序,因?yàn)镠ammer.js是會(huì)按順序遍歷識(shí)別器調(diào)用他們的recognize方法,因?yàn)槲覀円呀?jīng)設(shè)置了手勢(shì)的互斥,Hammer.js為了知道手勢(shì)是單擊還是雙擊,singleTap,doubleTap,tripleTap識(shí)別器都設(shè)置了300ms等待時(shí)間來(lái)判斷之后還會(huì)不會(huì)有點(diǎn)擊事件,根據(jù)識(shí)別順序,singleTap總能獲取tripleTap和doubleTap的識(shí)別結(jié)果來(lái)判斷是否要觸發(fā)事件,假如我們不設(shè)置他們之間的互斥關(guān)系,Hammer.js默認(rèn)一滿足條件就會(huì)觸發(fā),就會(huì)出現(xiàn)剛才說(shuō)的那種尷尬的場(chǎng)景。
那recognizeWith有啥作用尼,看以下代碼:

    if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
          curRecognizer = session.curRecognizer = null;
        }
    
        let i = 0;
        while (i < recognizers.length) {
          recognizer = recognizers[i];
          if (session.stopped !== FORCED_STOP && (
                  !curRecognizer || recognizer === curRecognizer || 
                  recognizer.canRecognizeWith(curRecognizer))) {
            recognizer.recognize(inputData);
          } else {
            recognizer.reset();
          }
          if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
            curRecognizer = session.curRecognizer = recognizer;
          }
          i++;
        }

雖然singleTap,doubleTap和tripleTap從最終結(jié)果上應(yīng)該是互斥的,但是同樣的數(shù)據(jù)輸入時(shí)可能會(huì)同時(shí)讓幾個(gè)手勢(shì)識(shí)別器識(shí)別,例如當(dāng)用戶點(diǎn)擊一下屏幕,singleTap識(shí)別器的狀態(tài)可能是STATE_RECOGNIZED或者STATE_BEGAN(等待doubleTap和tripleTap識(shí)別器的結(jié)果),session會(huì)把singTap識(shí)別器記錄為當(dāng)前的手勢(shì)識(shí)別器,但是doubleTap和tripleTap也是需要記錄一些狀態(tài)(例如當(dāng)前點(diǎn)擊次數(shù)),因?yàn)楹苡锌赡芙酉聛?lái)又是一個(gè)單擊,變成雙擊手勢(shì);當(dāng)用戶接著再單擊一下,doubleTap識(shí)別器因?yàn)樵O(shè)置了recognizeWith(singleTap)和以協(xié)同singleTap識(shí)別數(shù)據(jù)輸入,然后doubleTap識(shí)別器開始進(jìn)入STATE_RECOGNIZED或者STATE_BEGAN(等待tripleTap識(shí)別器的結(jié)果),此時(shí)session當(dāng)前的手勢(shì)識(shí)別器就是doubleTap了,而singleTap識(shí)別器因?yàn)闆]有設(shè)置recognizeWith(doubleTap),會(huì)被重置。

一點(diǎn)小的細(xì)節(jié)

我們?cè)谛D(zhuǎn)一張圖片時(shí),如何實(shí)現(xiàn)旋轉(zhuǎn),怎么知道旋轉(zhuǎn)的角度尼?
再回到computeInputData方法,有這樣一行代碼獲取偏轉(zhuǎn)角度:

    ...
    let center = input.center = getCenter(pointers);
    ...
    input.angle = getAngle(offsetCenter, center);
    ...

再跟蹤一下getCenter方法:

     while (i < pointersLength) {
        x += pointers[i].clientX;
        y += pointers[i].clientY;
        i++;
      }
    
     return {
        x: round(x / pointersLength),
        y: round(y / pointersLength)
      };

很簡(jiǎn)單的算出手勢(shì)的中心位置,當(dāng)我們雙指旋轉(zhuǎn)時(shí),中心位置也會(huì)跟著移動(dòng),很容易計(jì)算出前后偏轉(zhuǎn)角度。

最后一點(diǎn)思考

Hammer.js都是在冒泡階段綁定事件處理器,為什么不在捕獲階段攔截事件尼,如果一個(gè)向右活動(dòng)的手勢(shì)被識(shí)別,后續(xù)的事件(如touchMove)已經(jīng)沒必要再傳給子節(jié)點(diǎn),完全可以在攔截的元素上處理,這樣性能上也應(yīng)該會(huì)有一點(diǎn)提升,挖個(gè)坑給自己以后實(shí)現(xiàn)一下。
最后的最后。。。
因?yàn)闆]有使用經(jīng)驗(yàn),單靠啃源碼,難免有所錯(cuò)漏,望指正。

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

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

相關(guān)文章

  • Hammer.js源碼解析(1) - 整體架構(gòu)

    摘要:一直以來(lái)都想著去詳細(xì)了解手勢(shì)事件相關(guān)的東西,又因?yàn)橐恢币詠?lái)使用的都是,所以想著通過(guò)閱讀的源碼來(lái)學(xué)習(xí)手勢(shì)的相關(guān)知識(shí)。 一直以來(lái)都想著去詳細(xì)了解手勢(shì)事件相關(guān)的東西,又因?yàn)橐恢币詠?lái)使用的都是Hammer.js,所以想著通過(guò)閱讀Hammer.js的源碼來(lái)學(xué)習(xí)手勢(shì)的相關(guān)知識(shí)。 首先,我們來(lái)看Hammer.js的整體架構(gòu)(Hammer.js的版本都是2.0.8)showImg(https://se...

    source 評(píng)論0 收藏0
  • [譯] 在 Angular 中使用 HammerJS (觸摸手勢(shì))

    摘要:是一個(gè)為應(yīng)用添加觸摸手勢(shì)的非常受歡迎的庫(kù)文中將看到結(jié)合一起使用是多么的簡(jiǎn)單原文示例是針對(duì)版本經(jīng)過(guò)測(cè)試在目前最新的版本中此教程依然適用文章將以來(lái)統(tǒng)一代稱版本名詞滑動(dòng)和類似但滑動(dòng)更快速無(wú)粘滯左滑右滑上滑下滑頭像輪播簡(jiǎn)介我們將構(gòu)建一個(gè)頭像輪播可以 HammerJS 是一個(gè)為 web 應(yīng)用添加觸摸手勢(shì)的非常受歡迎的庫(kù),文中,將看到 Angular 結(jié)合 HammerJS 一起使用是多么的簡(jiǎn)單 ...

    lifesimple 評(píng)論0 收藏0
  • Vue源碼解析(5)-virtual-dom 實(shí)現(xiàn)簡(jiǎn)析

    傳送門vdom原理

    darcrand 評(píng)論0 收藏0
  • hammerjs的初始化發(fā)生了什么

    摘要:一個(gè)移動(dòng)端的手勢(shì)庫(kù)。的過(guò)程最簡(jiǎn)單的使用一個(gè)手勢(shì)的定義綁定事件調(diào)用初始化在中可以看到下面一段代碼用于定義一個(gè)手勢(shì)操作的元素定義配置參數(shù)定義如果為默認(rèn)默認(rèn)同步注冊(cè)了同理同步注冊(cè)也可以外部采用注冊(cè)同步綁定事件中的實(shí)際上為調(diào)用中的 hammerjs ———— 一個(gè)移動(dòng)端的手勢(shì)庫(kù)。 New Hammer 的過(guò)程 最簡(jiǎn)單的使用一個(gè)手勢(shì)的demo // 定義 Manager var hammer...

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

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

0條評(píng)論

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