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

資訊專欄INFORMATION COLUMN

FastClick 原理解析

QiShare / 2184人閱讀

摘要:一直好奇是如何工作,于是花了幾天空余的時(shí)間一步步調(diào)試代碼,學(xué)習(xí)。一般會(huì)在時(shí)暫停游戲存檔等操作。這樣即使不監(jiān)聽(tīng)事件也能實(shí)現(xiàn)點(diǎn)擊的偵聽(tīng)。這種情況下一定會(huì)觸發(fā)事件。

Patience and perseverance will get paid.

這段時(shí)間開(kāi)始實(shí)習(xí)了,在公司做hybrid,專職寫(xiě)js,學(xué)習(xí)到了不少東西。一直好奇fastclick是如何工作,于是花了幾天空余的時(shí)間一步步調(diào)試代碼,學(xué)習(xí)fastclick。這篇文章可以結(jié)合者代碼看,希望可以給予需要學(xué)習(xí)fastclick的人一點(diǎn)思路。

有錯(cuò)誤的地方希望指正,thk~

主流程

FastClick.attach()

FastClick(layer)

初始化化變量

this.trackingClick = false; //追蹤一個(gè)click
this.trackingClickStart = 0; //追蹤時(shí)間
this.targetElement = null; // 目標(biāo)元素
this.touchStartX = 0;// X坐標(biāo)
this.touchStartY = 0;// y坐標(biāo)
this.lastTouchIndentifier = 0;
this.touchBoundary = 10;//邊界條件(是否是一個(gè)點(diǎn)擊)
this.layer = layer;//layer可以是document.body/document.documentElement

安卓設(shè)備綁定鼠標(biāo)事件(在捕獲階段,為的是第一時(shí)間處理到事件)

layer.addEventListener("mouseover",bind(this.onMouse,this),true);
layer.addEventListener("mousedown",bind(this.onMouse,this),true);
layer.addEventListener("mouseup",bind(this.onMouse,this),true);

綁定touch和click事件(判定是否是click行為,取消之前的click),

//最先捕獲到
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);

判斷是否存在stopImmediatePropagation,如果不存在則進(jìn)行hack,在onMouse中會(huì)利用stopImmediatePropagation來(lái)阻止其他點(diǎn)擊事件的回調(diào)函數(shù)的執(zhí)行,避免ghost click的現(xiàn)象

onMouse中,防止點(diǎn)透等詭異現(xiàn)象的代碼

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

判斷是否通過(guò)onclick綁定了回調(diào)函數(shù),如果有讀取出來(lái),使用addEventListener,綁定事件處理函數(shù)

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

觸發(fā)click流程 onTouchStart()

當(dāng)一些更高級(jí)別的事件發(fā)生的時(shí)候(如電話接入或者彈出信息)會(huì)取消當(dāng)前的touch操作,即觸發(fā)ontouchcancel。一般會(huì)在ontouchcancel時(shí)暫停游戲、存檔等操作。因此在調(diào)試的時(shí)候才會(huì)在touchStart之后,就觸發(fā)了touchCancel

直接斷點(diǎn)touchend,在控制臺(tái)打印,可以看到touchstart也是觸發(fā)的了、

判斷是不是單點(diǎn)觸發(fā)

if (event.targetTouches.length > 1) {
    return true;
}

獲取目標(biāo)對(duì)象和touch事件對(duì)象

targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];

根據(jù)touch事件對(duì)象,設(shè)置一些初始屬性

this.trackingClick = true; //標(biāo)識(shí)跟蹤該次點(diǎn)擊
this.trackingClickStart = event.timeStamp;//點(diǎn)擊開(kāi)始的時(shí)間
this.targetElement = targetElement;//目標(biāo)元素

this.touchStartX = touch.pageX; //x坐標(biāo)
this.touchStartY = touch.pageY; //y坐標(biāo)

onTouchMove()

如果剛觸發(fā)完touchstart事件馬上就觸發(fā)touchend,說(shuō)明手指只是輕輕點(diǎn)了一下屏幕,也就是所謂的點(diǎn)擊操作。這樣即使不監(jiān)聽(tīng)click事件也能實(shí)現(xiàn)點(diǎn)擊的偵聽(tīng)。不過(guò)這里有一個(gè)實(shí)際的情況,很多山寨的Android設(shè)備屏幕很不靈敏,需要使勁按下才能有所感知。這種情況下一定會(huì)觸發(fā)touchmove事件。所以針對(duì)Android設(shè)備的點(diǎn)擊操作可以適當(dāng)放寬,比如touchstart和touchend之間可以允許有少量幾個(gè)touchmove,并且touchmove的距離不能超過(guò)多少個(gè)像素等等

因此也是需要監(jiān)聽(tīng)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()

在touchend的時(shí)候,執(zhí)行this.onTouchEnd(上個(gè)流程綁定了)

判斷是否在追蹤該click,在this.onTouchMove的時(shí)候,如果移動(dòng)的距離大于邊界,則將this.trackingClick=false,在touchend就不用再判斷是否為一個(gè)click的行為

if(!this.trackingClick){
    return true;
}

獲取目標(biāo)元素標(biāo)簽,需要根據(jù)標(biāo)簽名來(lái)做一些判斷

targetTagName = targetElement.tagName.toLowerCase();

如果是label,進(jìn)行bug修復(fù)

執(zhí)行this.needsFocus,針對(duì)表單元素的focus和click事件的處理

先f(wàn)ocus表單

在觸發(fā)點(diǎn)擊事件

針對(duì)IOS,滾動(dòng)層bug修復(fù)

判斷元素是否需要原生的click,實(shí)際上就是有些行為還是要瀏覽器來(lái)執(zhí)行默認(rèn)的行為

表單元素disabled,點(diǎn)擊不了

type=file的控件

video

label

如果不需要,則發(fā)送一個(gè)click事件

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

sendClick()流程

在一些安卓設(shè)備上,必須讓一個(gè)元素blured,才能使創(chuàng)建的clickEvent生效

if (document.activeElement && document.activeElement !== targetElement) {
    document.activeElement.blur();
}

創(chuàng)建clickEvent,使用touch事件對(duì)象的屬性來(lái)進(jìn)行初始化

clickEvent = document.createEvent("MouseEvents");
clickEvent.initMouseEvent(
        this.determineEventType(targetElement), //bug修復(fù)針對(duì)select
        true, 
        true, 
        window, 
        1, 
        touch.screenX, 
        touch.screenY, 
        touch.clientX, 
        touch.clientY, 
        false, false, false, false, 0, null);

創(chuàng)建完成之后,賦予對(duì)象一個(gè)額外的屬性,在onClick中可以使用,然后觸發(fā)點(diǎn)擊事件,此時(shí)通過(guò)addEventListner綁定的click事件就會(huì)觸發(fā)

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

onClick()

addEventListener添加會(huì)按照添加順序執(zhí)行

onClick作為第一個(gè)注冊(cè)監(jiān)聽(tīng)的,因此,是第一個(gè)執(zhí)行的click事件的回調(diào)函數(shù)

特殊情況處理,一般不會(huì)執(zhí)行

/*
    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;
}

特殊情況處理

if (event.target.type === "submit" && event.detail === 0) {
    return true;
}

執(zhí)行onMouse,

//創(chuàng)建時(shí),附帶的一個(gè)屬性
if (event.forwardedTouchEvent) {
    return true;
}

最后返回為真

return permitted; //true

注意:在這里的return的true或false并不會(huì)影響綁定的其他回調(diào)函數(shù)的執(zhí)行

總結(jié)

完整的看完代碼,深深感覺(jué)到移動(dòng)端的坑非常的多,很有怪異的現(xiàn)象因?yàn)闆](méi)有遇到過(guò)暫時(shí)理解不了,希望之后可以繼續(xù)研究,把代碼完全讀懂。

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

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

相關(guān)文章

  • 2019 再聊移動(dòng)端 300ms 延遲及 fastClick 原理解析

    摘要:前言最近公司新開(kāi)了一條業(yè)務(wù)線,有幸和大佬們一起從頭開(kāi)始構(gòu)建一套適合新業(yè)務(wù)的框架。俗話說(shuō)得好呀,適合自己的才是最好的 前言 最近公司新開(kāi)了一條業(yè)務(wù)線,有幸和大佬們一起從頭開(kāi)始構(gòu)建一套適合新業(yè)務(wù)的框架。俗話說(shuō)得好呀,適合自己的才是最好的

    skinner 評(píng)論0 收藏0
  • 2019 再聊移動(dòng)端 300ms 延遲 -- 附 fastClick 原理解析

    摘要:前言最近公司新開(kāi)了一條業(yè)務(wù)線,有幸和大佬們一起從頭開(kāi)始構(gòu)建一套適合新業(yè)務(wù)的框架。俗話說(shuō)得好呀,適合自己的才是最好的前言 (Foreword) 最近公司新開(kāi)了一條業(yè)務(wù)線,有幸和大佬們一起從頭開(kāi)始構(gòu)建一套適合新業(yè)務(wù)的框架。俗話說(shuō)得好呀,適合自己的才是最好的

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

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

0條評(píng)論

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