[譯] 讓滾動在預設情況下變得更流暢

weixin_33895657發表於2017-03-14

原文: https://developers.google.com/web/updates/2017/01/scrolling-intervention

我們知道滾動靈敏度對於手機端使用web站點的使用者沉浸度至關重要,但touch事件監聽器經常會引起嚴重的滾動效能問題.Chrome已經通過允許touch事件監聽器變為passive(被動的)(將{passive: true}傳遞給addEventListener())的方式和嚴格管理pointer事件 API來處理這種問題了.這是一些很讚的特性,讓新的內容進入模型當中,並且不阻塞住滑動,但是開發者們有些時候發現這很難理解和採納.

我們相信,網頁應該自然而然的很快,而不是需要開發者理解晦澀難懂的瀏覽器行為細節.在Chrome 56中,我們將touch監聽器預設設為passive,為的就是吸引開發者們的注意.我們相信通過這麼做,能夠很大程度上提升使用者體驗,並且儘量縮減站點上的負面體驗.

在極個別的場景下,這項調整可能引起預料之外的滾動.這通常可以簡單的通過應用touch-action: none樣式到那些不該出現滾動的元素上.繼續閱讀來了解更多細節,比如怎樣知道你是否被影響到了;假如是,你該怎麼做.

背景: 可取消的事件讓你的頁面變得更慢

檢視對比視訊

56687-744455baa78cbb8d.png

如果在touchstart或首次touchmove事件中呼叫了preventDefault()方法,那麼你將會阻止滾動事件.問題是大部分監聽器不會去呼叫preventDefault()方法,但是瀏覽器需要等待該事件結束來確保它會不會呼叫.針對開發者定義的"passive event listeners"解決了這個問題.當你向一個touch事件新增了{passive: true}物件作為事件處理器的第三個引數時,代表著你告訴瀏覽器這個touchstart事件將不會呼叫preventDefault(),然後瀏覽器就會以不阻塞的方式安全地進行滾動了.例如:

window.addEventListener("touchstart", func, {passive: true} );

干涉

我們的主要動機是降低使用者觸控螢幕之後更新展示內容的時間.為了理解touchstart和touchmove的使用,我們增加了度量資料來弄清滾動阻塞行為出現的有多頻繁.

我們檢視了被髮送到根目標(window,document,或body)的可取消觸控事件的百分比,並且弄清了大概80%的事件監聽器理論上是被動的,但是我們沒有刻意將它們變為這樣.考慮到這個問題的規模,我們意識到通過讓這些事件自動變成"passive"而不是通過開發者,將會有巨大的機會來提高滾動效能.

這驅使我們定義瞭如下的干涉規則: 如果touchstart或touchmove監聽器的目標是window,documentbody,我們預設將passive設為true.這意味著這樣的程式碼:

window.addEventListener("touchstart", func);

變得等價於:

window.addEventListener("touchstart", func, {passive: true} );

現在在監聽器內部呼叫preventDefault()將會被忽略.

下面的圖示展示了出現頻率最高的1%的滾動操作所花費的時間, 該時間代表一個使用者從觸控螢幕滑動到展示更新完成.這份資料針對於所有在安卓Chrome中執行的網頁.在這項干涉開啟之前,1%的滾動操作花費略高於400ms.但是現在在Chrome 56 Beta中已經下降為略高於250ms;這個降幅大概有38%.未來我們希望讓所有的touchstarttouchmove監聽器預設都是被動的,這樣能將該時間降低至50ms.

56687-8b0170c8c25146b6.png

破壞性和指導意見

在大量的用例中,並沒有觀測到破壞性行為.但是當意外來臨的時候,最常見的現象表現為當你不想滾動的時候,反而滾動了.在很少的案例中,開發者同樣也發現了一些預料之外的click事件(當preventDefault()touch監聽器中缺失的時候).

在Chrome 56和之後的版本,DevTools將會輸出一個警告,比如當干涉生效的時候你在事件中呼叫了preventDefault().

touch-passive.html:19 
Unable to preventDefault inside passive event listener due to target being treated as passive. 
See https://www.chromestatus.com/features/5093566007214080

你的應用能夠通過某種檢查來斷定它是否在某些地方會踩到這個雷.這種檢查通過defaultPrevented
屬性來判斷呼叫preventDefault是否有任何的影響.

我們已經發現了大量受影響的頁面可以通過touch-actionCSS屬性來修復.如果你想要在元素上阻止所有的瀏覽器滾動和縮放操作,那麼加一個touch-action: none.如果你有一個水平的跑馬燈並考慮在上面應用touch-action: pan-y pinch-zoom,然後使用者就能夠像往常一樣在垂直方向上進行滾動和縮放.在支援Pointer Events和非Touch Events的瀏覽器上,比如桌面版的Edge上正確的應用touch-action已然變得非常必要了.對移動端的Safari和不支援touch-action的老版移動瀏覽器,你的touch監聽器必須繼續呼叫preventDefault方法,即使該方法已經被Chrome忽略了.

在更復雜的案例中,可能要依賴下面的方法:

  • 如果touchstart監聽器呼叫了preventDefault(),確保在相對應的touchend監聽器中也同樣呼叫了preventDefault()方法,從而確保能夠阻止click事件和其它預設的點選行為的產生.

  • 最次的(也是不建議的)方法是顯式的將{passive: false}傳入到addEventListener()中來覆蓋預設的瀏覽器行為.需要注意的是如果瀏覽器支援EventListenerOptions,那麼你需要做特性檢測.

總結

在Chrome 56版本中,在很多網站上的滾動效能已經有了較為可觀的提升.最大的影響就是大多數的開發者將會意識到這項改變的結果.在一些案例中,開發者可能會注意到那些非預期的滾動.

儘管對於移動端的Safari來說這仍然很有必要,網站不應該依賴在touchstarttouchmove監聽器上呼叫preventDefault()來阻止預設行為.因為在Chrome中,這樣做已經不被支援和提倡了.開發者應該在那些需要禁用滾動和縮放的元素上增加touch-actionCSS屬性,好在任何touch事件出現前通知到瀏覽器.為了阻止tap(就像是一個click事件的產生)的預設行為,在touchend監聽器中呼叫preventDefault().

相關文章