ios局部滾動的坑及解決方案

原由

最近幾天在寫一個滾動加載更多數據的插件(Scrollload),爲局部滾動寫demo時,遇到了不少局部滾動的坑,在這裏分享一下這些坑的解決方案。如下的坑只針對ios。javascript

約定

把產生滾動條的元素稱之爲視窗 html

全局滾動:滾動條在body或者body父級元素上。java

局部滾動:滾動條在body裏的子孫元素上。ios

局部滾動優點

  1. 每一個局部滾動擁有本身的滾動條,這是全局滾動所不能取代的。最典型的是列子如局部滾動,全局滾動。不難發現全局滾動不一樣的tab之間共享一個滾動條,也就是說其中一個tab滾動了,另外一個tab也會跟着滾動。git

  2. 能夠解決input fixed定位失效問題。github

  3. 全局滾動有出界狀況,出界就是滑到最頂端和最底端後繼續滑。這樣會出現一個很噁心的效果。局部滾動雖然也會有這個狀況,可是能修復,全局滾動至少我不會修。web

圖片描述

坑(一)

ios瀏覽器局部滾動默認沒有彈性滾動的效果。解決方法是爲body或者視窗加-webkit-overflow-scrolling: touch。其實加這兩個地方都同樣,雖然在文檔中並無說該屬性有繼承性的,不過我在safari下測試出來是有繼承性的。該屬性具體說明看這裏瀏覽器

坑(二)

問題

先看一下視頻效果:沒用ScrollFix app

ios局部滾動的出界狀況,當你的滾動條在最頂端的時候,你會發現此時你的列表再也不滾動而是產生全局滾動了。其實這個確實應該是這樣的。若是此時你的視窗佔了比整個window還要大,就會一直在視窗裏滾動,你還讓不讓用戶看其餘內容了。但視窗的滾動條在最頂端的時候的時候下滑又迅速上滑你會發現仍是在作全局滾動。這個也應該是這樣的,全局滾動還沒停下來不可能作局部滾動吧。同理當你滾動條在最下面的時候也會出現這樣的情況。但有時候,你就不會想以上的效果。dom

解決方案

其實解決方案很簡單,既然知道了問題是滾動條在最頂端和最底端的時候纔會出現的,那麼你只要在touchstart的時候判斷scrollTop是否爲這兩個值,若是是就加1或者減1。這裏有一個別人已經實現的庫ScrollFix。視頻效果:用了ScrollFix。貼一下核心代碼

var ScrollFix = function(elem) {
    var startY, startTopScroll;
    elem.addEventListener('touchstart', function(event){
        startY = event.touches[0].pageY;
        startTopScroll = elem.scrollTop;
        //當滾動條在最頂部的時候
        if(startTopScroll <= 0)
            elem.scrollTop = 1;
        //當滾動條在最底部的時候
        if(startTopScroll + elem.offsetHeight >= elem.scrollHeight)
            elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;
    }, false);
};

可是這個庫必定要謹慎用。由於他監聽了touchstart事件,這個事件會使滾動滯後(在這裏並不明顯),passive event listeners。固然你不能用文章裏的解決方法,不然若是你快速滑動,因爲touchstart事件的監聽函數還沒執行到就已經開始滾動了因此可能仍是會發生上面的狀況,ScrollFix這個庫就無效了。

坑(三)

仍是先看一下視頻效果: 有問題的視頻

這個坑就是你的內容不滿視窗一個屏幕的時候,你向上滑動你會發現整個視窗都動了,也就成了全局滾動。這個現象是正常的,內容都不滿一屏固然不須要滾動啊,甚至連滾動條都沒產生。

解決方案

理由也說了,內容不足一個屏幕產生的現象,那麼讓內容時刻保持在一屏之上不就能夠了。這裏我寫了一個庫,LocalScrollFix。貼一下核心代碼

update() {
      //當內容超過一屏的時候isArrived爲true
        if (this.isArrived) {
            return
        }
      //每次調用update方法時候都去更新fixDom的paddingTop值
        const fixDomPaddingTop = this.computerFixDomPaddingTop()
        
        if (fixDomPaddingTop >= 0) {
              //只有當計算後的值大於0纔要更新
            this.fixDom.style.paddingTop = `${fixDomPaddingTop + 2}px`
        } else {
          //當計算後的值小於0的時候,也就是原來的內容就超過一屏幕了。arrived方法中會把fixDom移除。
            this.arrived()
        }
    }
    //計算fixDom所須要的paddingTop值
    computerFixDomPaddingTop() {
          //fixDom指的是append到最底部的dom。win指的是視窗
        const {fixDom, win} = this
        //一開始想內容的高度+paddingTop==數創的高度。由於直接求內容的高度其實並不簡單。
        //因此稍微變通了如下,讓窗口的top值減去fixDom的top值其實就是fixDom的paddingTop值
        //在把視窗的borderBottomWidth和視窗的paddingBottom考慮進去
        const fixDomTop = fixDom.getBoundingClientRect().top
        const winBottom = win.getBoundingClientRect().bottom
        const {paddingBottom: winPaddingBottom, borderBottomWidth: winBorderBottomWidth}= window.getComputedStyle(win, null)
        return winBottom - parseFloat(winPaddingBottom) - parseFloat(winBorderBottomWidth) - fixDomTop
    }

大概就是在視窗內append一個元素,當你調用update方法的時候就去更新這個元素的paddingTop值來使視窗的內容超過一屏。

最佳實踐

ios下能夠用局部滾動替代全局滾動,安卓用全局滾動(老的安卓手機在局部滾動上貌似有嚴重的性能問題)。可能不少人會擔憂這樣比較麻煩,其實仔細思考一下並無增長多少代碼。能夠參考最佳實踐,源碼

順便說一下,若是你使用Scrollload來做爲滾動到底部加載的插件,那麼坑二就只須要把配置useScrollFix設置爲true,坑三隻須要把配置useLocalScrollFix設置爲true。

相關文章
相關標籤/搜索