端300ms點(diǎn)擊延遲的解決方案)
上一份工作交接的時(shí)候我接手了一個(gè)快兩年沒怎么大改的移動(dòng)端H5項(xiàng)目。照例先翻package.json做技術(shù)摸底看到react-fastclick還躺在 dependencies 里說(shuō)實(shí)話心里咯噔了一下。這個(gè)庫(kù)在我上一個(gè)項(xiàng)目里早就被刪干凈了但等我再仔細(xì)翻代碼發(fā)現(xiàn)項(xiàng)目里保留了不少針對(duì)舊內(nèi)核WebView的兼容邏輯于是又猶豫了到底該不該繼續(xù)用 react-fastclick 來(lái)應(yīng)對(duì)移動(dòng)端 300ms 點(diǎn)擊延遲這個(gè)問題沒有一句“早就不需要了”那么簡(jiǎn)單。如果你也在維護(hù)老項(xiàng)目或者新項(xiàng)目里還在糾結(jié)要不要引入這個(gè)庫(kù)這篇文章把 300ms 點(diǎn)擊延遲的來(lái)龍去脈、FastClick 的前世今生、以及現(xiàn)代移動(dòng)端瀏覽器到底做了什么改進(jìn)全部攤開講清楚。你只要對(duì)照自己的項(xiàng)目場(chǎng)景就能得出明確結(jié)論不用再為這個(gè)事兒開會(huì)討論。1. 300ms點(diǎn)擊延遲的來(lái)龍去脈1.1 雙擊縮放是怎么把延遲“發(fā)”給全世界的2007 年 iPhone 發(fā)布的時(shí)候移動(dòng)端的網(wǎng)頁(yè)交互其實(shí)處在一個(gè)非常尷尬的階段。桌面端頁(yè)面直接搬到手機(jī)上字太小用戶需要雙擊才能把某個(gè)區(qū)塊放大看清內(nèi)容。為了保證“第一次點(diǎn)擊”和“第二次點(diǎn)擊”能被區(qū)分開——也就是系統(tǒng)需要判斷你到底是想雙擊縮放還是單純地單擊某個(gè)按鈕——Safari 在瀏覽器內(nèi)核里加了一個(gè)等待機(jī)制第一次觸摸事件發(fā)生之后不立刻派發(fā) click而是等 300ms 左右確認(rèn)你沒有第二次點(diǎn)擊才把 click 事件發(fā)出來(lái)。這個(gè) 300ms 的等待邏輯本質(zhì)上是給用戶一個(gè)手勢(shì)選擇的“猶豫期”。后來(lái) Android 上的 Chrome 和各個(gè)第三方瀏覽器幾乎都復(fù)刻了同樣的行為因?yàn)殡p擊縮放在當(dāng)時(shí)是移動(dòng)端瀏覽網(wǎng)頁(yè)的基礎(chǔ)交互沒有它用戶根本沒法閱讀那些沒為手機(jī)優(yōu)化過的頁(yè)面。等移動(dòng)端H5開發(fā)成為主流之后大家才意識(shí)到這個(gè)“猶豫期”在按鈕點(diǎn)擊場(chǎng)景里有多難受。關(guān)鍵點(diǎn)在于300ms 不是手機(jī)硬件的觸摸采樣延遲也不是 WebView 渲染的卡頓而是瀏覽器主動(dòng)設(shè)置的一個(gè)事件派發(fā)等待窗口。所以它跟手機(jī)性能好壞關(guān)系不大iPhone 15 和 iPhone 6 上如果不做處理點(diǎn)擊事件的延遲感受是一樣的。1.2 300ms延遲真正的影響范圍有多大說(shuō)起來(lái)300ms 好像也就是一眨眼的功夫但在真實(shí)交互里體感非常明顯。你點(diǎn)一個(gè)按鈕按鈕本身沒有反應(yīng)大約 0.3 秒之后才開始觸發(fā)視覺反饋用戶的第一感覺就是“卡”“不跟手”。尤其在做移動(dòng)端性能優(yōu)化的時(shí)候整個(gè)頁(yè)面可能渲染已經(jīng)優(yōu)化到 60fps 了結(jié)果點(diǎn)擊事件還帶著 300ms 的鐵鏈前端的交互體驗(yàn)拉胯優(yōu)化其他東西都白搭。更讓人頭疼的是衍生出來(lái)的“點(diǎn)擊穿透”問題。移動(dòng)端彈窗的關(guān)閉按鈕、輪播圖的切換箭頭、表單里的提交按鈕這些高頻點(diǎn)擊區(qū)域一旦有 300ms 延遲用戶就容易產(chǎn)生二次點(diǎn)擊、甚至三次點(diǎn)擊。層疊結(jié)構(gòu)下上面圖層點(diǎn)擊后消失下面圖層在同位置又接收到了這個(gè) click就出現(xiàn)了關(guān)閉彈窗的同時(shí)把底下某個(gè)按鈕也一起觸發(fā)的情況。這也是為什么當(dāng)年 FastClick 那么火的直接原因——它不只消除延遲還順手解決點(diǎn)擊穿透。從數(shù)值上算一個(gè)用戶一天在移動(dòng)端H5上點(diǎn)擊按鈕、鏈接、Tab 至少幾十次每次多等 0.3 秒累積起來(lái)就是十幾秒的白白等待。這些等待體感不會(huì)直接報(bào)錯(cuò)但跳出率會(huì)誠(chéng)實(shí)地告訴你問題有多嚴(yán)重。2. 現(xiàn)代瀏覽器早就把這個(gè)延遲“干掉了”2.1 viewport設(shè)置對(duì)了一半問題就沒了很多 React 項(xiàng)目里的public/index.html都寫著一行meta nameviewport contentwidthdevice-width, initial-scale1.0但這行代碼在消滅 300ms 點(diǎn)擊延遲這件事上作用被嚴(yán)重低估了。Google 的 Chrome 團(tuán)隊(duì)在 2013 年就提出了一個(gè)方案當(dāng)頁(yè)面設(shè)置了widthdevice-width的 viewport意味著頁(yè)面寬度與設(shè)備寬度一致用戶不需要雙擊縮放來(lái)閱讀內(nèi)容那瀏覽器可以直接關(guān)閉雙擊縮放檢測(cè)自然也就不用等待 300ms。這個(gè)改動(dòng)真正落地是在 2014 年左右發(fā)布的 Chrome 32 上Android 端的 Chrome 從此開始對(duì)“沒有啟用縮放”的頁(yè)面取消 300ms 延遲。iOS 的 Safari 走得稍微慢一點(diǎn)從 iOS 9.32016年開始才跟隨這個(gè)邏輯。只要 viewport 里的width等于device-widthSafari 就不會(huì)再等待雙擊手勢(shì)。到 2017 年主流的 iOS 和 Android 瀏覽器已經(jīng)全部完成了這項(xiàng)調(diào)整。所以你發(fā)現(xiàn)沒有如果你的 H5 頁(yè)面從一開始就設(shè)置了正確的 viewport 且沒有開放用戶縮放那 300ms 延遲在現(xiàn)代瀏覽器里根本不存在。讓你產(chǎn)生“點(diǎn)擊不跟手”感覺的往往是別的因素比如頁(yè)面卡頓、事件綁定延遲、或者你在舊內(nèi)核的 WebView 里測(cè)試。2.2 touch-action: manipulation一行CSS的降維打擊如果擔(dān)心 viewport 沒被正確設(shè)置、或者有些不走尋常路的瀏覽器不認(rèn)這一套那就直接用 CSS 的touch-action屬性來(lái)做兜底。html, body { touch-action: manipulation; }這一行代碼告訴瀏覽器這個(gè)頁(yè)面允許用戶進(jìn)行 pan 和 pinch 縮放但不要為“雙擊縮放”做等待。瀏覽器收到指令后會(huì)直接跳過雙擊手勢(shì)檢測(cè)點(diǎn)擊事件立刻派發(fā)。相比引入 FastClick這幾乎是零成本、零維護(hù)的降維打擊。touch-action的兼容性也相當(dāng)不錯(cuò)。iOS Safari 9.3、Chrome 36、Android WebView 36 全都支持。如果你的項(xiàng)目最低要兼容到五六年前的移動(dòng)端瀏覽器這一行 CSS 也基本夠用。唯一的風(fēng)險(xiǎn)是touch-action本身會(huì)改變?yōu)g覽器對(duì)手勢(shì)行為的一些判定但在manipulation這個(gè)值上它允許所有滾動(dòng)和捏合縮放保留頁(yè)面所有默認(rèn)能力,只是移除了雙擊縮放等待適合絕大多數(shù)頁(yè)面場(chǎng)景。2.3 Pointer Events新一代點(diǎn)擊事件協(xié)議除了 CSS 層面DOM 事件層面也有了一個(gè)統(tǒng)一方案Pointer Events。這個(gè)協(xié)議把鼠標(biāo)事件、觸摸事件、觸控筆事件統(tǒng)一成一套pointerdown、pointerup、pointermove并且由瀏覽器自己決定是否需要等待手勢(shì)判定。在實(shí)際業(yè)務(wù)里絕大多數(shù)現(xiàn)代框架React、Vue的onClick底層仍然綁定的是 click 事件所以 Pointer Events 更多是給那些需要“超低延遲”反饋的場(chǎng)景使用的。比如一個(gè)拖動(dòng)滑塊、繪圖板、或者高頻點(diǎn)擊的按鈕你可以監(jiān)聽pointerdown而不是click這樣事件在手指接觸屏幕的一瞬間就會(huì)觸發(fā)而不是等觸摸結(jié)束再觸發(fā)。這在交互手感上差距非常明顯。當(dāng)然直接全面切換到pointerdown也有新問題它不具備 click 的“容錯(cuò)性”用戶在頁(yè)面滑動(dòng)時(shí)一開始的pointerdown也會(huì)被誤判為點(diǎn)擊。所以更務(wù)實(shí)的做法是常規(guī)按鈕繼續(xù)用 click touch-action: manipulation只有對(duì)延遲特別敏感的自定義組件才用pointerdown自己控制手感。3. react-fastclick的功勞與歷史包袱3.1 FastClick的工作原理react-fastclick 本質(zhì)上不是 React 組件它是 FastClick 庫(kù)的 React 封裝。FastClick 的原始思路可以用一句話概括在瀏覽器還死守著 300ms 延遲的年代不等原生的 click 事件而是自己監(jiān)聽觸摸事件在touchend之后立刻派發(fā)一個(gè)合成 click 事件。它的大致流程是這樣的在 document 上監(jiān)聽touchstart和touchend記錄觸摸開始和結(jié)束的位置與時(shí)間。如果觸摸位移很小基本沒有滑動(dòng)并且時(shí)長(zhǎng)很短比如小于某個(gè)閾值就判定這次觸摸是“點(diǎn)擊”立即在目標(biāo)元素上觸發(fā)一個(gè)合成 click。與此同時(shí)它內(nèi)部還會(huì)阻止瀏覽器隨后派發(fā)的原生 click避免同一個(gè)點(diǎn)擊被執(zhí)行兩次。在當(dāng)時(shí)那個(gè)瀏覽器環(huán)境碎片化嚴(yán)重的年代FastClick 確實(shí)解決了很多實(shí)際問題。不管是在 iOS 還是 Android 的舊瀏覽器里引入它之后點(diǎn)擊響應(yīng)立刻跟手用戶體感提升非常明顯。一個(gè)庫(kù)能被幾乎全行業(yè)廣泛采用并且一度成為移動(dòng)端H5項(xiàng)目的標(biāo)配說(shuō)明它的價(jià)值在當(dāng)時(shí)是貨真價(jià)實(shí)的。3.2 它解決問題同時(shí)也在制造新問題FastClick 被詬病最多的問題集中發(fā)生在表單交互上。iOS 下點(diǎn)擊 input 輸入框偶爾會(huì)出現(xiàn)“無(wú)法聚焦、需要點(diǎn)多次才彈出鍵盤”的怪異現(xiàn)象社區(qū)里大量 issue 都指向 FastClick 與輸入框聚焦事件狀態(tài)機(jī)之間的沖突。很多人不得不額外加一段 hack 代碼來(lái)修復(fù)。第二個(gè)副作用是它會(huì)影響瀏覽器原生的一些手勢(shì)判定。如果你在頁(yè)面上實(shí)現(xiàn)了一個(gè)自定義滾動(dòng)容器或者拖拽組件FastClick 的 touch 監(jiān)聽有可能把手勢(shì)期間的一次“輕微位移”誤判成“快速點(diǎn)擊”結(jié)果拖拽剛啟動(dòng)就觸發(fā)了點(diǎn)擊回調(diào)。這些場(chǎng)景排查起來(lái)非常耗時(shí)間因?yàn)樗皇潜噩F(xiàn)的跟用戶的觸摸軌跡、手指濕度都有關(guān)系。第三個(gè)問題是技術(shù)棧層面的。React 17 把事件委托的掛載點(diǎn)從 document 改成了應(yīng)用的根容器FastClick 攔截 document 級(jí) click 的機(jī)制在某些情況下會(huì)和 React 的事件系統(tǒng)產(chǎn)生沖突。前端框架迭代到 React 18、19 之后react-fastclick 這個(gè)庫(kù)已經(jīng)基本停更代碼倉(cāng)庫(kù)里的 issue 長(zhǎng)時(shí)間沒人回復(fù)。你在 npm install 它的時(shí)候可能還會(huì)看到一堆因?yàn)橐蕾囘^老而出現(xiàn)的警告。3.3 react-fastclick的現(xiàn)狀與維護(hù)情況我特意去翻了一下 react-fastclick 和 FastClick 的 npm 及 GitHub 倉(cāng)庫(kù)。FastClick 本體最后一次正式發(fā)布停留在 2016 年之后react-fastclick 的更新節(jié)奏同樣緩慢最近幾年的變化主要是為了適配 React 的版本兼容補(bǔ)丁還是靠社區(qū)貢獻(xiàn)者在維護(hù)。這不代表它已經(jīng)完全不可用你的項(xiàng)目只要鎖住了 React 版本它也許還能跑得不錯(cuò)但問題在于它的設(shè)計(jì)初衷是為了解決“老瀏覽器普遍有 300ms 延遲”的年代問題而這個(gè)前提在今天已經(jīng)不存在了。拿一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)今天你新開一個(gè) React 18 的移動(dòng)端H5項(xiàng)目如果只面向現(xiàn)代手機(jī)瀏覽器占比超過 95%引入 react-fastclick 等于給代碼庫(kù)增加了一個(gè)不必要的第三方依賴還額外承擔(dān)了它和 React 事件系統(tǒng)“磨合”的風(fēng)險(xiǎn)。你維護(hù)的每行依賴都應(yīng)該有它存在的理由而不是因?yàn)椤耙郧耙恢边@么用”就一直留著。4. 你的項(xiàng)目到底該不該繼續(xù)用4.1 先做一次項(xiàng)目環(huán)境“體檢”要回答“該不該用”需要先搞清楚你的項(xiàng)目跑在什么環(huán)境下。我建議從下面幾個(gè)角度做一次快速體檢頁(yè)面 HTML 里有沒有設(shè)置meta nameviewport contentwidthdevice-width, initial-scale1.0viewport 如果缺失絕大多數(shù)現(xiàn)代瀏覽器依然可能保留雙擊縮放檢測(cè)。你的用戶是用系統(tǒng)瀏覽器打開還是內(nèi)嵌在某個(gè) App 的 WebView 里微信內(nèi)置瀏覽器、企業(yè) App 的 WebView它們的瀏覽器內(nèi)核更新時(shí)間一般滯后于系統(tǒng)瀏覽器。你實(shí)際需要兼容的最低內(nèi)核版本是什么如果項(xiàng)目后臺(tái)統(tǒng)計(jì)里還有大量 Android 7 以下的老設(shè)備訪問或者公司內(nèi)部 App 的 WebView 是基于多年前的 X5 內(nèi)核那情況要另說(shuō)。頁(yè)面上有沒有大面積的文本縮放需求如果你的產(chǎn)品允許用戶縮放頁(yè)面來(lái)讀書那就不能直接關(guān)閉瀏覽器縮放能力需要更精細(xì)地管理手勢(shì)和事件派發(fā)。一個(gè)很粗暴的判斷方法是把 react-fastclick 從代碼里摘掉加上touch-action: manipulation用真機(jī)跑一輪核心操作流程看點(diǎn)擊反應(yīng)是否滿足產(chǎn)品預(yù)期。如果滿足就不需要再引入如果不滿足再考慮針對(duì)性方案。4.2 不同場(chǎng)景下的推薦方案場(chǎng)景推薦方案現(xiàn)代H5項(xiàng)目viewport正確目標(biāo)用戶設(shè)備較新不引庫(kù)加一行touch-action: manipulation即可老App內(nèi)嵌WebView內(nèi)核版本接近現(xiàn)代系統(tǒng)瀏覽器不引庫(kù)先用 CSS 兜底真機(jī)測(cè)試后再做決定老App內(nèi)嵌WebView基于多年前的定制內(nèi)核可以暫時(shí)保留 react-fastclick同步打算治理兼容層頁(yè)面需要支持復(fù)雜的滑動(dòng)和縮放交互不使用 FastClick改用 Pointer Events 自己控制手勢(shì)判定React 組件庫(kù)自帶觸摸反饋盡量使用組件庫(kù)的觸摸事件不額外疊全局庫(kù)4.3 如果決定移除怎么安全下線如果你的項(xiàng)目里還在用 react-fastclick經(jīng)過體檢后決定移除我建議按下面步驟操作每一步都做回歸驗(yàn)證不要在凌晨發(fā)布前臨時(shí)刪依賴。第一步全局搜索代碼里所有FastClick或react-fastclick的引用理清楚它是在入口文件統(tǒng)一初始化還是在多個(gè)頁(yè)面里分散調(diào)用。很多老項(xiàng)目只在入口文件調(diào)用了一次FastClick.attach(document.body)搜索起來(lái)很快。第二步移除依賴并刪除初始化代碼同時(shí)在全局樣式里加上touch-action: manipulation。這里要強(qiáng)調(diào)可以先加 CSS、不刪 JS發(fā)一版到測(cè)試環(huán)境觀察沒問題再?gòu)氐讋h這種灰度策略雖然多了一次發(fā)布但能把風(fēng)險(xiǎn)拆分得很干凈。第三步重點(diǎn)回歸表單交互。盡可能在 iOS 和 Android 的真機(jī)上測(cè)一遍輸入框聚焦、鍵盤彈起、下拉選擇這些操作確保沒有出現(xiàn) FastClick 曾經(jīng)帶來(lái)的“點(diǎn)一次沒反應(yīng)”的問題。第四步手動(dòng)觸發(fā)一遍“快速連點(diǎn)”場(chǎng)景。比如彈窗里的“確認(rèn)”按鈕用戶手滑雙擊不要讓同一個(gè)彈窗回調(diào)執(zhí)行兩次。如果產(chǎn)品上確實(shí)有防止誤觸的需求直接加一個(gè)按鈕級(jí)別的 loading 狀態(tài)或者節(jié)流處理比依賴事件庫(kù)更可靠。5. 移動(dòng)端點(diǎn)擊相關(guān)的兩個(gè)高頻衍生問題5.1 ECharts在移動(dòng)端“點(diǎn)不動(dòng)”“滑不動(dòng)”是怎么回事很多團(tuán)隊(duì)把 ECharts 圖表嵌到移動(dòng)端H5里上了線上之后就收到反饋“我點(diǎn)圖上的柱狀圖沒反應(yīng)”“tooltip 不出來(lái)”“圖表頁(yè)面左右滑動(dòng)特別卡”。這里要注意ECharts 在移動(dòng)端的點(diǎn)擊問題和 300ms 點(diǎn)擊延遲其實(shí)是兩回事。ECharts 默認(rèn)使用 Canvas 渲染圖表本身是畫在 canvas 上的一個(gè)“整體”。整個(gè)圖表區(qū)域在 DOM 上只有一個(gè) canvas 節(jié)點(diǎn)ECharts 內(nèi)部會(huì)自己計(jì)算你點(diǎn)的是哪根柱子、哪個(gè)坐標(biāo)、哪個(gè)數(shù)據(jù)項(xiàng)。如果你的頁(yè)面或者父級(jí)容器設(shè)置了某些 touch-action 攔截或者 canvas 的觸摸事件沒有正確綁定到 ECharts 的 zrender 實(shí)例上就會(huì)出現(xiàn)點(diǎn)不到數(shù)據(jù)項(xiàng)的情況。比較實(shí)用的排查思路是先在 PC 端用開發(fā)者工具模擬移動(dòng)端確認(rèn)圖表交互本身是好的。然后到真機(jī)上打開 vConsole在頁(yè)面上插入一個(gè)調(diào)試面板監(jiān)看一下 touch 事件到底有沒有觸發(fā)是被哪一層元素?cái)r截了。ECharts 的坐標(biāo)系轉(zhuǎn)換在移動(dòng)端有時(shí)候會(huì)有一點(diǎn)偏移如果你用了tooltip的 HTML 模式還需要給 tooltip 特殊設(shè)置 position否則它可能被 canvas 區(qū)域的 overflow 裁剪掉。對(duì)于“圖表區(qū)域滑不動(dòng)頁(yè)面”的問題可以給圖表容器設(shè)置touch-action: pan-y讓垂直方向的滾動(dòng)交給頁(yè)面處理水平方向留給圖表操作。5.2 點(diǎn)擊穿透問題在頁(yè)面里的排查技巧點(diǎn)擊穿透是 300ms 延遲時(shí)代最經(jīng)典的衍生 bug。雖然現(xiàn)代瀏覽器已經(jīng)沒有延遲但如果你還在老內(nèi)核 WebView 里或者頁(yè)面上有透明遮罩、定位浮層依然可能踩到。我自己排查這類問題時(shí)第一件事是打開 vConsole在移動(dòng)端瀏覽器里注入一個(gè)調(diào)試面板方便直接看控制臺(tái)日志確認(rèn)到底哪個(gè)元素最終接收了 click 事件。排查思路分三步打開 vConsole 后觀察點(diǎn)擊浮層和底層元素時(shí)控制臺(tái)輸出的 touchstart、touchend、click 順序然后檢查浮層元素的pointer-events屬性確認(rèn)它是否在某些狀態(tài)下變成了 none導(dǎo)致事件穿透到了底層最后看底部元素的點(diǎn)擊事件綁定是否用了事件委托如果是縮小委托范圍或改用數(shù)據(jù)屬性判斷目標(biāo)來(lái)源。常用的修復(fù)方案有三種。第一在浮層關(guān)閉后的同一幀內(nèi)給底層元素臨時(shí)設(shè)置pointer-events: none再在下次點(diǎn)擊時(shí)恢復(fù)這是最直接的方式。第二把浮層關(guān)閉的動(dòng)畫和事件派發(fā)錯(cuò)開等動(dòng)畫完全結(jié)束后再允許點(diǎn)擊。第三如果整個(gè)頁(yè)面都在統(tǒng)一的觸摸事件模式控制下可以放棄原生 click統(tǒng)一用touchend配合位移判斷來(lái)實(shí)現(xiàn)按鈕點(diǎn)擊但這套方案成本更高不建議在常規(guī)頁(yè)面上引入。6. 常見問題速查表問題結(jié)論新移動(dòng)端H5項(xiàng)目還要引入 react-fastclick 嗎絕大多數(shù)不需要除非你的目標(biāo)環(huán)境是老內(nèi)核 WebViewFastClick 還兼容 React 18/19 嗎庫(kù)基本停更官方?jīng)]有明確適配社區(qū)有補(bǔ)丁但建議慎用一行 CSS 能替代整個(gè)庫(kù)嗎t(yī)ouch-action: manipulation在現(xiàn)代瀏覽器上可以舊內(nèi)核需要測(cè)試移除 react-fastclick 后點(diǎn)擊還是慢檢查 viewport、touch-action、頁(yè)面渲染性能不要盲目重裝庫(kù)ECharts 移動(dòng)端點(diǎn)擊不生效多半不是 300ms 延遲優(yōu)先排查 canvas 和 touch-action 沖突頁(yè)面上輸入框聚焦不穩(wěn)定如果裝了 FastClick先考慮移除它觀察效果部分頁(yè)面想兼容極老瀏覽器怎么辦可以做按需降級(jí)只在檢測(cè)到老內(nèi)核時(shí)動(dòng)態(tài)引庫(kù)這里我想額外提一句在排查移動(dòng)端點(diǎn)擊問題時(shí)一定要先確認(rèn)問題規(guī)模。如果只是某個(gè)頁(yè)面上的某個(gè)按鈕點(diǎn)擊慢大概率是頁(yè)面渲染卡頓或者事件綁定位置不對(duì)如果是整個(gè)應(yīng)用所有按鈕都有明顯延遲再往 300ms 延遲和瀏覽器內(nèi)核方向考慮。不要一上來(lái)就引入一個(gè)全局庫(kù)引入了之后再排查問題反而會(huì)多一個(gè)變量。另外很多團(tuán)隊(duì)在遷移到 React 18 之后發(fā)現(xiàn) react-fastclick 的綁定行為有點(diǎn)“怪”順手把它換成了 CSS 方案結(jié)果真機(jī)上毫秒級(jí)的點(diǎn)擊響應(yīng)反而比之前還好。這也在預(yù)料之中因?yàn)楝F(xiàn)代瀏覽器對(duì) click 的派發(fā)已經(jīng)不再有長(zhǎng)等待把原生事件交給瀏覽器去處理永遠(yuǎn)比第三方庫(kù)模擬事件更可靠。7. 我踩過幾次坑之后的感想從最早在 jQuery 項(xiàng)目里用 FastClick到 React 項(xiàng)目里用 react-fastclick再到現(xiàn)在新項(xiàng)目里已經(jīng)完全不引入這個(gè)庫(kù)我自己經(jīng)歷了完整的三個(gè)階段。回過頭來(lái)看這類“問題解決庫(kù)”的生命周期其實(shí)很有參考價(jià)值它誕生于一個(gè)特定的歷史環(huán)境解決了當(dāng)時(shí)普遍存在、但今天已經(jīng)被技術(shù)棧自身進(jìn)化掉的問題。我現(xiàn)在接到老項(xiàng)目代碼評(píng)審看到 react-fastclick 還掛在 dependencies 里時(shí)默認(rèn)流程是先檢查一遍頁(yè)面的實(shí)際運(yùn)行環(huán)境做一個(gè)瀏覽器內(nèi)核版本占比統(tǒng)計(jì)然后在測(cè)試環(huán)境里直接去掉這個(gè)庫(kù)、加上一行 CSS跑一輪自動(dòng)化和真機(jī)回歸。如果指標(biāo)沒有下降就直接推進(jìn)刪除。如果頁(yè)面確實(shí)有相當(dāng)一部分用戶還在用老內(nèi)核 WebView那我會(huì)把 react-fastclick 限定在“僅老內(nèi)核環(huán)境按需加載”的降級(jí)策略里而不是讓所有用戶都背這份包袱。最后再分享一個(gè)實(shí)用小技巧移動(dòng)端點(diǎn)擊這件事不要只盯著事件庫(kù)。手指按下去到頁(yè)面響應(yīng)涉及硬件觸摸采樣、系統(tǒng)手勢(shì)識(shí)別、瀏覽器事件派發(fā)、DOM 事件回調(diào)、React 狀態(tài)更新與渲染多階段任何一個(gè)環(huán)節(jié)慢都會(huì)導(dǎo)致“點(diǎn)擊不跟手”。先打開瀏覽器的 Performance 面板錄制一段時(shí)間用戶操作看看從touchstart到click派發(fā)的時(shí)間差是多少以及點(diǎn)擊后 React 組件狀態(tài)更新耗時(shí)多少。數(shù)據(jù)擺出來(lái)誰(shuí)慢就優(yōu)化誰(shuí)比直接引一個(gè)第三方庫(kù)靠譜得多。