iOS 與 慣性滾動


注:以下所有例子均  在 iOS 的微信中測試過,但對於餓了么APP的內置瀏覽器同樣適用(兩者使用相同內核)

引題

工作中常常有需要顯示大量信息的情況,列表超出一屏就涉及到滾動的問題。例如

- var n = 1
ul  
  while n <= 100
    li= n++

在 iOS 中用微信打開,滾動非常順滑,so far so good!但某天產品需求有變,要求加一個固定在頭部的標題,於是改成這樣:

- var n = 1
h1= "Momentum Scrolling on iOS"  
ul  
  while n <= 100
    li= n++
body, ul { margin: 0; } html, body { height: 100%; } body { display: flex; flex-direction: column; } h1 { flex-shrink: 0; } ul { flex: 1; overflow: scroll; } 

直接用 flex 盒模型實現,動態適應標題的高度,很簡單不是么。但是這時在 iOS 上打開后測試,發現有問題,下半部分區域滾動起來感覺很不順滑,用老板的話說就像“卡齒輪”

這時就有大牛推薦了傳說中的神器:-webkit-overflow-scrolling: touch

ul { flex: 1; overflow: scroll; -webkit-overflow-scrolling: touch; } 

很簡單的一個屬性,順滑滾動效果就回來了!雖然不太明白是怎么回事,解決問題就好。 但是產品經理又說了,需要在滾動時獲取滾動條的位置做些其他操作。太簡單了,加個 scroll 事件搞定。

document.querySelector('ul').addEventListener('scroll', function() { this.previousElementSibling.textContent = 'ScrollTop: ' + this.scrollTop; }) 

隨手寫好在瀏覽器中測試通過,然而在手機上測試就不太對勁:那個值是會變,然而滾動的時候不變,只有在滾動結束后變一次。

整個滾動過程中 scroll 事件只在滾動結束后會被觸發一次,問題是出在這個所謂的神器 -webkit-overflow-scrolling 上面

-webkit-overflow-scrolling 究竟是什么鬼?

一個只有 iOS 設備支持的非標准屬性。蘋果自己的解釋:指定是否在 overflow: scroll 的元素中使用“原生”的滾動方式

他包含兩個可選值:auto 和 touch

  • auto:就是普通的無慣性滾動效果
  • touch:原生的滾動效果。使用此效果會構造一個 stacking context

什么是 stacking context?這可以說是CSS里一個陰暗面,極其晦澀。有興趣的朋友可以去看高人的解釋,這里不做討論(其實筆者自己也不是非常明白:cry:),總之所有的坑都是由此而起。

-webkit-overflow-scrolling 引發了那些坑?

下面列出我遇到過的坑:

滾動中 scrollTop 屬性不會變化。

嚴格來說,上面的 scroll 事件不觸發只是本坑的一個副作用,所以說不必考慮通過 touchmove 事件轉發 scroll 事件等點子,scroll 事件觸發了一樣檢測不到 scrollTop 屬性的變化(當然檢測手指的移動距離另說)。同樣,檢測滾動區域內部元素的 getBoundingClientRect 同樣無效。

例中起了一個無限的rAF循環不停地獲取 scrollTop 的值,然並卵。

手勢可穿過其他元素觸發元素滾動

這個更奇葩。例中用一個半透明的 div 蓋在了滾動區域 ul 上面(實踐中可能是一個彈框的背部蒙版),甚至給 ul 自己加上了 pointer-events: none,手指在 div 上滑動仍然會觸發 ul 的滾動。你可以在顯示半透明蒙版時將 ul 的 -webkit-overflow-scrolling: touch 或 overflow: scroll 去掉,但是會造成屏幕明顯的閃爍。如果給 body 的 touchmove事件 preventDefault() 可以防止觸發滾動,但是是所有滾動區域都會失效。

運行時通過 JS 動態添加元素溢出高度導致滾動失效

Google 上一搜一片,但是筆者沒有遇到過,或許在新版本系統中已經修正,這里不展開討論。

滾動時暫停其他 transition

還有沒有其他未踩的坑呢?

……

有沒有什么好的解決方案

使用 WKWebView 替換 UIWebView 內核

可能有些讀者已經發現,scroll 事件不能觸發的坑在 iOS Safari 和 iOS Chrome 瀏覽器中不存在,為什么呢?這里要從 iOS 上瀏覽器的發展史說起。

由於蘋果公司對安全性等原因的考慮,蘋果公司靜止第三方瀏覽器在 iOS 設備上使用自己的瀏覽器的內核,換句話說,使用自己內核的瀏覽器都被禁止上架 AppStore。各大廠商無奈,於是長久以來,包括 Chrome 在內的所有第三方瀏覽器,都只是使用 iOS 系統內置的瀏覽器控件包一層外殼,這個控件就是 UIWebView。這個 UIWebView 不僅速度差,HTML5 支持率低,占用內存高,還有各種各樣奇怪的問題。然而蘋果公司卻給自己的 Safari 瀏覽器開了后門。首先 Safari 使用的支持 JIT 編譯的 JS 引擎內核 Nitro 比 UIWebView 里老舊的解釋性 JavaScriptCore 內核速度搞數倍,然后 HTML5 支持度也比 UIWebView 高,還少了某些奇葩bug。久而久之就形成了 iOS 設備上 Safari 瀏覽器全面碾壓其他第三方瀏覽器的現象。

在喬幫主撒手人寰不久之后,蘋果公司口氣終於松動,雖然沒有放開第三方瀏覽器內核的限制,但把 Safari 的瀏覽器內核提取了出來開放第三方瀏覽器使用,那就是如今的 WKWebView(WK 即 Webkit 的縮寫)。但由於 WKWebView 只支持 iOS8 以上系統,各大瀏覽器廠商並未立刻跟進。直到最近的 iOS9 時代,Chrome 成為第一個吃螃蟹的 APP,使用了 WKWebView 內核。測試數據表明,使用 WKWebView 內核的 Chrome 瀏覽器在速度和 HTML5 支持率上已經與 Safari 瀏覽器不相上下。緊接着 Mozilla 公司宣布 Firefox 登錄 iOS 平台,使用的也是 WKWebView 內核(於是有了第一款基於 Webkit 內核的火狐瀏覽器 :)

不知蘋果做了什么手腳,也許蘋果的開發人員認為 WKWebView 的效能已經足以支撐在 scroll 事件中執行額外代碼而不造成 UI 卡頓,總之在 WKWebView 內核中滾動可以正常觸發 scroll 事件,當然也能正常獲得 scrollTop 的值。然而經過測試第二個問題仍然存在。

在這里筆者強烈建議各個 APP 遷移內嵌瀏覽器至新的 WKWebView 內核。但是就我看到的,包括微信和餓了么在內,幾乎所有的國產 APP 都還在使用 UIWebView 內核,這不得不說是一大前端開發之殤。

自己實現一套滾動邏輯

比如前段時間很火的 iScroll,筆者曾近也使用過一段時間。最后得出的結論是:iScroll 挖出的坑不比它填上的坑少,比如在 iScroll 里加個 click 事件都要小心翼翼、特別對待(因為絕大多數情況綁定 touch 事件的回調函數里第一件做的事情就是 preventDefault)。

最值得一提的是 iScroll 的速度問題,比原生實在相差太多,在中低端安卓機型上卡頓明顯,如果還要綁定 scroll 事件做些別的事情就更卡了。

總之筆者並不建議使用。

放棄 H5,擁抱 Native

也許這才是真正的終極解決之道


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM