新年第一發--深入不淺出zepto的Tap擊穿問題

dasea_sky 7年前發布 | 6K 次閱讀 Zepto.js 移動開發

問題來源

年前去阿里面試,過程中說道了fastclick解決iPhone機器上300ms點擊延遲的問題,然后就被問到了zepto的“點擊穿透”的現象以及產生這個具體原因,當時回答的不是很好,主要是沒有特別深入的去研究這個原因,只是知道有這個現象和問題,大概怎么解決,面試完了之后有一天突然想起來了,就決定仔細的研究下。

其實有好多文章都寫了,內容有很多我就不重復,總結以下幾點:

  1. 300ms延遲是由于瀏覽器要判斷是單機還是雙擊造成的延遲處理點擊事件

  2. fastclick解決方式用touchstart結合touchmove以及touchend替代click事件

  3. zepto的tap會“擊穿”頁面是由于既響應了自身的tap(也就是touch事件),又沒有攔截掉原來的click事件,導致重復執行了2次事件,在有遮罩彈層的時候就會出現“擊穿”效果。如果不太明白的話看這篇文章 zepto的擊穿

年前探究

當時研究到這里時候我有一個大大的疑問就是為什么click延遲執行之后,遮罩層下面的頁面的click事件會被觸發,我明明點擊的遮罩層的A按鈕,為何下面頁面的B按鈕的事件會執行。按照我最初的想法,應該是繼續執行A按鈕的事件啊!!!此時我內心是這樣的

于是我開始探究這個問題,我搜了下大概的資料,基本都沒有講這個具體原因的,也許是我打開方式不對,反正沒有找到,無奈之下,我只能翻看fastclick的源碼來看它為何沒有出現這個問題,然后看到了sendClick的代碼,心里猛然有了一個猜想。

FastClick.prototype.sendClick = function(targetElement, event) {
    var clickEvent, touch;
    // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
    if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
    }
    touch = event.changedTouches[0];
    // Synthesise a click event, with an extra attribute so it can be tracked
    clickEvent = document.createEvent('MouseEvents');
    clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent);
    };

注意這里的initMouseEvent,當時就在想肯定和mouseEvent執行的原理有關了,到這個階段算是有了眉目。

接著搞

緊接著,開始過年,過年期間享受了生活,并沒有碰代碼和文檔(好墮落的感覺......),加上我跳槽的空檔和折騰,年后稍稍穩定下來了,最近又想起了年前這探究一半的猜想,開始繼續搞了起來,順便收收心,好進入狀態。

先說猜想--click事件最開始其實在瀏覽器當中被捕捉的時候,只有mouseEvent的相關屬性,也就是我們平常在console.log(event)的一部分,之后,瀏覽器才會結合html,js產生我們常說的click時間,接著觸發我們使用js綁定的函數。

一般情況的event的各種屬性

基于這個猜想,我開始翻閱 mozilla W3C 的文檔來了解mouseEvent。

翻看文檔之后發現mouseEvent果然只有 screenX,screenY,clientX,clientY,ctrlKey,altKey,shiftKey,metaKey,button,buttons,EventTarget?relatedTarget。

其中button和buttons指的是鼠標的按鈕類型,就是左鍵,右鍵,滾輪這些。用數字代替,0表示左鍵,1是滾輪,2是右鍵,其他更多功能鍵,都是大于2的。

從上面我們能看出來,其實對于mouseEvent而言,它只知道我們在屏幕的哪個位置,做了什么動作(鼠標操作),并不知道是在哪個element上面。這也就是fastclick還原用戶點擊事件最后做的事情。

clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
// detremineEvenType是fastclick封裝返回mouseEvent的type類型,就是click還是mouseDown

初始化一個鼠標事件,然后dispatch這個鼠標事件。瀏覽器自動響應后續處理。

接著來看click的定義,如下圖所示:

click的屬性

click會多了Event.target,而且必須是一個 topmost event target ,在mozilla定義有些不太相同,多了currentTarget和type等。

mozilla的click

先來看EventTarget的定義:EventTarget is an interface implemented by objects that can receive events and may have listeners for them.

Element, document, and window are the most common event targets, but other objects can be event targets too, for example XMLHttpRequest, AudioNode,AudioContext, and others.

從定義就能看出來了,如果是click事件必須要有一個target來承載這次鼠標事件。一般來說target要么是element要么是document,如果都沒有那么就是window對象了。到這里大家應該就比較明白,這里就是瀏覽器的 事件機制 了。

event-flow

這里就應該是initMouseEvent之后,瀏覽器干的事情,來尋找是否有target來響應此次事件,如果前面一直沒有target來響應,最后就會到window上,一般來說我們不會在window上做事件處理,就會沒有任何響應,事件結束了。如果碰巧的事,此時有target(一般來說就是element了)來響應,那么就會執行綁定的函數了。

總結下整個流程:用戶點擊屏幕,300ms之內,瀏覽器攔截下這個行為,沒有去真正觸發相關element上綁定的click事件執行函數,而是記錄操作相關數據,等待接下來的操作,由于我們使用zepto庫綁定了tap事件,事件中有監聽touchend觸發了,立刻執行相關操作,隱藏了彈層。300ms到了,瀏覽器認為這次動作是click而不是dbclick,然后init一次mouseEvent在相同的屏幕位置,接著開始事件機制,發現相同位置有一個element綁定了click處理函數,執行這個函數,Over!!!穿透就是這樣產生的。PS:瀏覽器行為部分是猜測,未驗證。

至于解決方案:網上有很多,目前最好的是fastclick,不過fastclick也會有其他問題,例如在滑動中點擊之類的。另外就是用zepto但是要preventDefault。

Android自己chrome已經解決了,可以用其他方式, 目前Safari也支持了,不過是在高版本上,相關討論可以看fastclick的 issue

 

來自:https://zhuanlan.zhihu.com/p/25280160

 

 本文由用戶 dasea_sky 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!