我是如何通過debug成功甩鍋瀏覽器的:解決fixed定位元素,在頁面滾動後touch事件失效問題

libinfs發表於2018-02-28

如果你關注我應該知道,我最近對PC端頁面進行移動適配。在這個過程中,為了節省使用者300ms的時間,同時給予使用者更及時的點選反饋(這意味著更好的使用者體驗),我在嘗試使用移動端獨有的 touchstart 事件替代傳統的 click 事件,這過程中我遇到了一些小問題,併成功解決了,你可以通過這篇文章檢視具體的情況。

所謂禍不單行,在即將釋出上線的時候,我又突然發現使用 touchstart 事件後,移動裝置上出現了另一個比較詭異的現象:當使用者滾動頁面後,原本繫結在fixed定位的navbar元素上的 touchstart 事件會時常失效。你可以通過掃描下方二維碼,並使用你的Safari瀏覽器或Chrome瀏覽器(注意:不是瀏覽器自帶的模擬器)親自感受這一奇怪的現象。

clipboard.png

當然,最終我成功解決了這個問題,並且有意思的是,這個問題似乎並不出自我的程式碼,而被我歸咎於是瀏覽器的Bug。但是對於這個Bug出現的原理,我也只有一個大概的推測,如果你清楚的知道產生這一現象的原因,也歡迎你和我分享。

在本篇文章中,我不但會記錄我的解決方案,並且會記錄我在遇到這個問題後的debug的過程與分析思路。不過如果你正被這個問題搞得焦頭爛額,只想快點擺脫這個問題,你可以直接翻閱到文章底部“解決方案”部分,參考我的解決方案(我真是貼心,對吧? ?)。

一 · 問題描述

  • 移動裝置:iPhone 6
  • 作業系統:iOS 11.2.5
  • 測試瀏覽器:Safari,Chrome

  • 點選此處檢視示例程式碼

當在移動裝置上使用測試瀏覽器開啟網頁並滾動螢幕後,會發現再次點選navbar,navbar元素繫結的 touchstart 事件並沒有被觸發(或偶爾不被觸發)。

二 · 分析排查

(一)步驟一:使用搜尋引擎

我是在無意中發現該問題的,當時觀察到的現象是繫結在navbar上的 touchstart 事件有時會被觸發,有時會失效。於是我使用Google搜尋了“touchstart 事件偶爾失效”的關鍵字,很遺憾,並沒有什麼靠譜的答案。這說明“並不存在touchstart偶爾失效的問題”。也就是說,我需要找到確切的令 touchstart 響應事件失效的原因。

接下來,我不斷的在移動裝置上嘗試各種操作(雙擊,滾動螢幕,放大等),並留心移動裝置的響應,最終將 touchstart 繫結事件失效的原因確定為在“頁面滾動之後”。接下來的事很簡單,繼續Google搜尋“touchstart 事件在頁面滾動後失效”。觀察首屏搜尋結果,並點選進去檢視,遺憾的是,並沒有什麼合適的資訊:

clipboard.png

接著我抱著試試看的態度切換百度搜尋同樣的關鍵詞,還不錯,已經有了一個相似度很高的搜尋結果,但是閱讀後發現依然不是我想要的:

clipboard.png

是時候上最終的大殺器了,使用英文關鍵字搜尋!以下是我使用了“touch event doesn't respond after page scroll”關鍵字的Google搜尋結果:

clipboard.png

老樣子,還是沒有令人滿意的結果。至此我獲得了兩點資訊:

  1. 可能是我的程式碼寫的有問題:因為很小可能出現奇怪的現象從來沒有人遇到過;
  2. 我應該從 touch 事件的相關概念上找原因;

(二)步驟二:隔離程式碼,明晰概念

至此,debug的第一階段結束,我的徒勞無功表明了這個問題並不簡單,我需要認真對待,接下來我採用了以下兩種方法:

  1. 抽離核心程式碼,通過隔離外界不確定因素,排除外因;
  2. 查閱MDN上touch事件的文件,查詢可能引發此類問題的內因;

思路很清晰對吧,但是我並沒有在相關文件中找到可能引發此問題的任何靈感。不過好在我通過第一步已經讓問題變得非常清晰了,不要灰心,繼續思考。


(三)步驟三:大膽假設,小心求證

基本上到了這個階段,debug的過程就進入到經驗和直覺領域了,要成功解決這個問題,你有時還需要一點點運氣,我在這個過程中嘗試了以下方案:

“無腦試對”:我將在搜尋引擎中看到的一些問題的解決方法,逐個試驗,希望有個會管用,我可以獲得更多資訊去定位問題出現的原因。這些嘗試有:為事件的回撥函式新增 e.preventDefault() 方法,替換 touchstart 事件為 touchend 事件或者直接是 click 事件。

很尷尬,這些嘗試都沒有起到作用,問題依然存在,不過沒有關係,我本來也沒有對這個簡短的嘗試抱太大的期望,不過這其實也說明這個奇怪的現象和touch具體的事件型別無關,和touch事件誤觸發其餘事件無關。

目前為止,我已經知道了 touch 事件我使用的方式是正確的,並且沒有其餘的因素可以干擾點選事件的觸發,自然而然的,我開始好奇,瀏覽器到底有沒有檢測到我手指的“點選”。這可以通過以下程式碼得到答案:

window.addEventListener("touchstart", e => {
    console.log(e.target)
})
複製程式碼

奇妙的事情發生了,我的navbar居然不再出現頁面滾動後touch事件失效的問題!但是當我按照相同的思路,將程式碼替換為下面的程式碼想要看看返回值時:


var navbar = document.querySelector(".navbar")

navbar.addEventListener("touchstart", e => {
    console.log(e.target)
})
複製程式碼

問題又出現了,並且當頁面滾動後,每當我再次點選navbar,控制檯沒有任何輸出,這意味著瀏覽器認為我並沒有點選navbar!

這不科學,但是我已然看到勝利的曙光。當我我將原先繫結在navbar上的 touchstart 事件通過事件的冒泡機制繫結在 window 物件,通過判斷 e.target 屬性進行事件回撥時 -- 問題解決了,頁面正常了,整個世界都清淨了...

最終的解決方案

程式碼如下:

var navbar = document.querySelector(".navbar")

window.addEventListener("touchstart", e => {
    if (e.target === navbar) {
        // callback
    }
})
複製程式碼

掃描二維碼檢視正確的效果:

clipboard.png

到此為止問題被成功解決了嗎?並沒有。

雖然世界清淨了的那一刻令人神清氣爽,但是這只是需求被實現了,問題並沒有被解決,我指的是我心裡的那個問題:“為什麼這樣就行,而原來那樣就不行?”。這個問題至關重要,也希望你們不要忽略。

你同意嗎?那讓我們繼續。

讓我們再回過頭分析一下我們的程式碼,很明顯它已經非常精簡了,唯一可能出問題的地方在於我們給navbar的 fixed 定位。我們再想想我們是怎麼“誤打誤撞”解決這個問題的,navbar檢測不到我們的點選,但是window可以,將這兩個線索放在一起思考,我得出了一個很值得懷疑的物件:層級。

我試著取消了navbar的 position: fixed; 宣告,果然,一切又都正常了。看來這一奇異現象的始作俑者就是這條宣告。而我能想到與之相關的因素就是層級,我是指DOM物件的層級

最終我是這樣解釋這個奇怪現象產生的原因:

在頁面初始化時,瀏覽器的DOM樹被正確的渲染,也就是說DOM元素間的關係正確,因此 navbar 元素可以準確的捕捉我們的 touchstart 事件,但是當頁面滾動後,瀏覽器丟失了 navbar 元素的層級關係, touchstart 事件無法通過冒泡被 navbar 元素捕捉,因此我們繫結的事件沒有響應。而當我們讓整個window物件監測 touchstart 事件後,瀏覽器可以重新正確的計算DOM物件間的關係,navbar 層可以捕捉到冒泡的事件,因此一切就都正常了。

這個解釋有說服你嗎?其實我心裡也沒個底,畢竟這只是我基於現象的一種推測。但無論如何,這種奇怪現象的發生,應該被歸咎於瀏覽器,而不是我的程式碼(哈,鬆了一口氣)。

到這裡,這個問題就結束了嗎?並沒有。如果只是憑一個現象,一個推測就甩鍋瀏覽器,會不會讓人有一種欽定的感覺,讓某些人不服呢?會的,我自己心就比較虛,不過沒關係,只要掌握了以下的關鍵訣竅,甩鍋瀏覽器還不是分分鐘的事。

該訣竅就是 -- 你自己去多測幾個瀏覽器啊,朋友!!

我們根據引擎區分不同瀏覽器,

  • 使用Webkit引擎的瀏覽器:Chrome,Safari
  • 使用Gecko引擎的瀏覽器:Firefox
  • 使用Presto引擎的瀏覽器:Opera

於是我下載了Firefox瀏覽器重新測試了原始碼下頁面效果,果然沒有問題!

呵呵,不是我程式碼的問題。都是使用Webkit引擎的瀏覽器不好 :)。

三 · 總結

你以為本篇文章就這麼結束了?並沒有(失望吧),實際上寫到這裡,你應該也有所感覺,雖然這次debug成功解決了問題,但整個過程並不流暢高效,並且在其中走了些許彎路。結合本次debug的經驗,我總結了以下幾個在下次debug過程中需要注意的方面:

  • 依次使用Google,Google(英文關鍵字),百度搜尋引擎搜尋問題關鍵字;
  • 對沒有搜尋到結果的問題保持警惕(檢查自己的程式碼);
  • 編寫Demo,隔離核心程式碼,簡化分析背景;
  • 確保掌握相關技術知識點;
  • 首先使用各瀏覽器測試(這樣便可以儘早排除是否是瀏覽器Bug);
  • 儘早使用 debugger 關鍵字而不是 console.log 方法進行除錯;(沒錯,debugger 關鍵字讓除錯更高效);
  • 仔細觀察問題,大膽假設,小心求證;
  • 不要放棄,在解決需求後,要解決問題;
  • 如果有時間精力的話,將發現的問題和debug過程中學到的知識總結一篇部落格吧 :);

以上,是我在實際開發過程中發現問題,分析問題並解決問題的過程和思路,希望對你們有幫助。

相關文章