如果你關注我應該知道,我最近對PC端頁面進行移動適配。在這個過程中,為了節省使用者300ms的時間,同時給予使用者更及時的點選反饋(這意味著更好的使用者體驗),我在嘗試使用移動端獨有的 touchstart
事件替代傳統的 click
事件,這過程中我遇到了一些小問題,併成功解決了,你可以通過這篇文章檢視具體的情況。
所謂禍不單行,在即將釋出上線的時候,我又突然發現使用 touchstart
事件後,移動裝置上出現了另一個比較詭異的現象:當使用者滾動頁面後,原本繫結在fixed定位的navbar元素上的 touchstart
事件會時常失效。你可以通過掃描下方二維碼,並使用你的Safari瀏覽器或Chrome瀏覽器(注意:不是瀏覽器自帶的模擬器)親自感受這一奇怪的現象。
當然,最終我成功解決了這個問題,並且有意思的是,這個問題似乎並不出自我的程式碼,而被我歸咎於是瀏覽器的Bug。但是對於這個Bug出現的原理,我也只有一個大概的推測,如果你清楚的知道產生這一現象的原因,也歡迎你和我分享。
在本篇文章中,我不但會記錄我的解決方案,並且會記錄我在遇到這個問題後的debug的過程與分析思路。不過如果你正被這個問題搞得焦頭爛額,只想快點擺脫這個問題,你可以直接翻閱到文章底部“解決方案”部分,參考我的解決方案(我真是貼心,對吧? ?)。
一 · 問題描述
- 移動裝置:iPhone 6
- 作業系統:iOS 11.2.5
- 測試瀏覽器:Safari,Chrome
- 點選此處檢視示例程式碼
當在移動裝置上使用測試瀏覽器開啟網頁並滾動螢幕後,會發現再次點選navbar,navbar元素繫結的 touchstart
事件並沒有被觸發(或偶爾不被觸發)。
二 · 分析排查
(一)步驟一:使用搜尋引擎
我是在無意中發現該問題的,當時觀察到的現象是繫結在navbar上的 touchstart
事件有時會被觸發,有時會失效。於是我使用Google搜尋了“touchstart 事件偶爾失效”的關鍵字,很遺憾,並沒有什麼靠譜的答案。這說明“並不存在touchstart偶爾失效的問題”。也就是說,我需要找到確切的令 touchstart
響應事件失效的原因。
接下來,我不斷的在移動裝置上嘗試各種操作(雙擊,滾動螢幕,放大等),並留心移動裝置的響應,最終將 touchstart
繫結事件失效的原因確定為在“頁面滾動之後”。接下來的事很簡單,繼續Google搜尋“touchstart 事件在頁面滾動後失效”。觀察首屏搜尋結果,並點選進去檢視,遺憾的是,並沒有什麼合適的資訊:
接著我抱著試試看的態度切換百度搜尋同樣的關鍵詞,還不錯,已經有了一個相似度很高的搜尋結果,但是閱讀後發現依然不是我想要的:
是時候上最終的大殺器了,使用英文關鍵字搜尋!以下是我使用了“touch event doesn`t respond after page scroll”關鍵字的Google搜尋結果:
老樣子,還是沒有令人滿意的結果。至此我獲得了兩點資訊:
- 可能是我的程式碼寫的有問題:因為很小可能出現奇怪的現象從來沒有人遇到過;
- 我應該從
touch
事件的相關概念上找原因;
(二)步驟二:隔離程式碼,明晰概念
至此,debug的第一階段結束,我的徒勞無功表明了這個問題並不簡單,我需要認真對待,接下來我採用了以下兩種方法:
- 抽離核心程式碼,通過隔離外界不確定因素,排除外因;
- 查閱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
}
})
掃描二維碼檢視正確的效果:
到此為止問題被成功解決了嗎?並沒有。
雖然世界清淨了的那一刻令人神清氣爽,但是這只是需求被實現了,問題並沒有被解決,我指的是我心裡的那個問題:“為什麼這樣就行,而原來那樣就不行?”。這個問題至關重要,也希望你們不要忽略。
你同意嗎?那讓我們繼續。
讓我們再回過頭分析一下我們的程式碼,很明顯它已經非常精簡了,唯一可能出問題的地方在於我們給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過程中學到的知識總結一篇部落格吧 :);
以上,是我在實際開發過程中發現問題,分析問題並解決問題的過程和思路,希望對你們有幫助。