FastClick 原理解析

Patience and perseverance will get paid.javascript

這段時間開始實習了,在公司作hybrid,專職寫js,學習到了很多東西。一直好奇fastclick是如何工做,因而花了幾天空餘的時間一步步調試代碼,學習fastclick。這篇文章能夠結合者代碼看,但願能夠給予須要學習fastclick的人一點思路。java

有錯誤的地方但願指正,thk~瀏覽器

主流程

  1. FastClick.attach()ide

  2. FastClick(layer)函數

  3. 初始化化變量學習

    this.trackingClick = false; //追蹤一個click
    this.trackingClickStart = 0; //追蹤時間
    this.targetElement = null; // 目標元素
    this.touchStartX = 0;// X座標
    this.touchStartY = 0;// y座標
    this.lastTouchIndentifier = 0;
    this.touchBoundary = 10;//邊界條件(是不是一個點擊)
    this.layer = layer;//layer能夠是document.body/document.documentElement
  4. 安卓設備綁定鼠標事件(在捕獲階段,爲的是第一時間處理到事件)ui

    layer.addEventListener('mouseover',bind(this.onMouse,this),true);
    layer.addEventListener('mousedown',bind(this.onMouse,this),true);
    layer.addEventListener('mouseup',bind(this.onMouse,this),true);
  5. 綁定touch和click事件(斷定是不是click行爲,取消以前的click),this

    //最早捕獲到
    layer.addEventListener('click', bind(this.onClick, this), true);
    //冒泡階段捕獲
    layer.addEventListener('touchstart', bind(this.onTouchStart, this), false);
    layer.addEventListener('touchmove', bind(this.onTouchMove, this), false);
    layer.addEventListener('touchend', bind(this.onTouchEnd, this), false);
    layer.addEventListener('touchcancel', bind(this.onTouchCancel, this), false);
  6. 判斷是否存在stopImmediatePropagation,若是不存在則進行hack,在onMouse中會利用stopImmediatePropagation來阻止其餘點擊事件的回調函數的執行,避免ghost click的現象spa

    onMouse中,防止點透等詭異現象的代碼調試

    if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
        // Prevent any user-added listeners declared on FastClick element from being fired.
        if (event.stopImmediatePropagation) {
            event.stopImmediatePropagation();
        } else {
            // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
            event.propagationStopped = true;
        }
        // Cancel the event
        event.stopPropagation();
        event.preventDefault();
        return false;
    }
  7. 判斷是否經過onclick綁定了回調函數,若是有讀取出來,使用addEventListener,綁定事件處理函數

    if (typeof layer.onclick === 'function') {
        // Android browser on at least 3.2 requires a new reference to the function in layer.onclick
        // - the old one won't work if passed to addEventListener directly.
        oldOnClick = layer.onclick;
        layer.addEventListener('click', function (event) {
            oldOnClick(event);
        }, false);
        layer.onclick = null;
    }

觸發click流程

onTouchStart()

當一些更高級別的事件發生的時候(如電話接入或者彈出信息)會取消當前的touch操做,即觸發ontouchcancel。通常會在ontouchcancel時暫停遊戲、存檔等操做。所以在調試的時候纔會在touchStart以後,就觸發了touchCancel

直接斷點touchend,在控制檯打印,能夠看到touchstart也是觸發的了、

  1. 判斷是否是單點觸發

    if (event.targetTouches.length > 1) {
        return true;
    }
  2. 獲取目標對象和touch事件對象

    targetElement = this.getTargetElementFromEventTarget(event.target);
    touch = event.targetTouches[0];
  3. 根據touch事件對象,設置一些初始屬性

    this.trackingClick = true; //標識跟蹤該次點擊
    this.trackingClickStart = event.timeStamp;//點擊開始的時間
    this.targetElement = targetElement;//目標元素
    
    this.touchStartX = touch.pageX; //x座標
    this.touchStartY = touch.pageY; //y座標

onTouchMove()

若是剛觸發完touchstart事件立刻就觸發touchend,說明手指只是輕輕點了一下屏幕,也就是所謂的點擊操做。這樣即便不監聽click事件也能實現點擊的偵聽。不過這裏有一個實際的狀況,不少山寨的Android設備屏幕很不靈敏,須要使勁按下才能有所感知。這種狀況下必定會觸發touchmove事件。因此針對Android設備的點擊操做能夠適當放寬,好比touchstart和touchend之間能夠容許有少許幾個touchmove,而且touchmove的距離不能超過多少個像素等等

所以也是須要監聽onTouchMove,而且加入判斷

// If the touch has moved, cancel the click tracking
if ( 
    this.targetElement !== this.getTargetElementFromEventTarget(event.target) 
    || this.touchHasMoved(event)
    ) {
    this.trackingClick = false;
    this.targetElement = null;
}

onTcouhEnd()

  1. 在touchend的時候,執行this.onTouchEnd(上個流程綁定了)

  2. 判斷是否在追蹤該click,在this.onTouchMove的時候,若是移動的距離大於邊界,則將this.trackingClick=false,在touchend就不用再判斷是否爲一個click的行爲

    if(!this.trackingClick){
        return true;
    }
  3. 獲取目標元素標籤,須要根據標籤名來作一些判斷

    targetTagName = targetElement.tagName.toLowerCase();
  4. 若是是label,進行bug修復

  5. 執行this.needsFocus,針對表單元素的focus和click事件的處理

    1. 先focus表單

    2. 在觸發點擊事件

  6. 針對IOS,滾動層bug修復

  7. 判斷元素是否須要原生的click,實際上就是有些行爲仍是要瀏覽器來執行默認的行爲

    1. 表單元素disabled,點擊不了

    2. type=file的控件

    3. video

    4. label

  8. 若是不須要,則發送一個click事件

    event.preventDefault();
    this.sendClick(targetElement, event);

sendClick()流程

  1. 在一些安卓設備上,必須讓一個元素blured,才能使建立的clickEvent生效

    if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
    }
  2. 建立clickEvent,使用touch事件對象的屬性來進行初始化

    clickEvent = document.createEvent('MouseEvents');
    clickEvent.initMouseEvent(
            this.determineEventType(targetElement), //bug修復針對select
            true, 
            true, 
            window, 
            1, 
            touch.screenX, 
            touch.screenY, 
            touch.clientX, 
            touch.clientY, 
            false, false, false, false, 0, null);
  3. 建立完成以後,賦予對象一個額外的屬性,在onClick中可使用,而後觸發點擊事件,此時經過addEventListner綁定的click事件就會觸發

    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent);

onClick()

addEventListener添加會按照添加順序執行

onClick做爲第一個註冊監聽的,所以,是第一個執行的click事件的回調函數

  1. 特殊狀況處理,通常不會執行

    /*
        It's possible for another FastClick-like library delivered with third-
        party code to fire a click event before FastClick does (issue #44). In 
        that case, set the click-tracking flag back to false and return early. 
        This will cause onTouchEnd to return early.
    */
    if (this.trackingClick) {
        this.targetElement = null;
        this.trackingClick = false;
        return true;
    }
  2. 特殊狀況處理

    if (event.target.type === 'submit' && event.detail === 0) {
        return true;
    }
  3. 執行onMouse

    //建立時,附帶的一個屬性
    if (event.forwardedTouchEvent) {
        return true;
    }
  4. 最後返回爲真

    return permitted; //true

注意:在這裏的return的true或false並不會影響綁定的其餘回調函數的執行

總結

完整的看完代碼,深深感受到移動端的坑很是的多,頗有怪異的現象由於沒有遇到過暫時理解不了,但願以後能夠繼續研究,把代碼徹底讀懂。

相關文章
相關標籤/搜索