iOS 與 慣性滾動

注:如下全部例子均  在 iOS 的微信中測試過,但對於餓了麼APP的內置瀏覽器一樣適用(二者使用相同內核)css

引題

工做中經常有須要顯示大量信息的狀況,列表超出一屏就涉及到滾動的問題。例如html

- 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 上打開後測試,發現有問題,下半部分區域滾動起來感受很不順滑,用老闆的話說就像「卡齒輪」ios

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

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

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

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

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

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

-webkit-overflow-scrolling 到底是什麼鬼?

一個只有 iOS 設備支持的非標準屬性。蘋果本身的解釋:指定是否在 overflow: scroll 的元素中使用「原生」的滾動方式微信

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

  • 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

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

相關文章
相關標籤/搜索