1. 程式人生 > >一次react滾動列表的實踐---相容ios安卓

一次react滾動列表的實踐---相容ios安卓

一、背景

近期專案改版,對原有的h5頁面進行了重新設計,資料呈現變成了瀑布流。希望新版相容ios和安卓兩端的情況下,無限制的重新整理載入資料。大致效果如下:

整個頁面分4部分:

  • 頂部導航
  • 步數狀態卡片
  • 使用者資訊卡片
  • 滾動列表

期望效果:列表滾動到使用者資訊卡片消失後,展示另一個吸頂的導航欄。

效果如下:


分析可以發現,首先我們要做的就是適配iPhone X,其次我們需要監聽列表的滾動高度,在pc和安卓上監聽滾動事件是沒有問題的,但是ios上滾動過程中不會觸發scroll事件,而是滾動結束後會觸發onscrollend事件,這就不能滿足實時監聽高度的要求。經過簡單調研,決定站在巨人的肩膀上,通過

iscrollbetter-scroll等js庫實現。這兩個庫都是解決各種滾動相容的js庫,很多常見的輪播、picker元件都是基於這些庫封裝的。順便說一句,還有個庫也不錯(simulation-scroll-y

二、進入正題

  • 1.適配iPhone X

PhoneX的適配,在iOS 11中採用了viewport-fit的meta標籤作為適配方案;viewport-fit的預設值是auto。react app的渲染內容都在id為root的 div裡面。我們給這個div加上iphoneX的safe-area-inset屬性即可。更多相關內容,這篇文章寫的挺詳細


<meta name='viewport' content='width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover' />

#root{
    height:100vh;
    padding-top: constant(safe-area-inset-top);
    padding-left: constant(safe-area-inset-left);
    padding-right: constant(safe-area-inset-right);
    padding-bottom: constant(safe-area-inset-bottom);
    padding-top: env(safe-area-inset-top);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
  }
  1. 2.頁面

    頁面結構不多說,比較基礎。
    div.container-> div#wrapper->div.list->div.list-item
    值得注意的是,wrapper需要設定絕對定位。同時,通過transform: translateZ(0);開啟硬體加速,瀏覽器在渲染時會通過GPU進行渲染。有效緩解安卓端滾動卡頓的問題,類似的css還有不少,css硬體加速不要濫用,否則會導致不該使用gpu的layer使用gpu,佔用記憶體過高,導致頁面卡頓,甚至黑屏,一般情況下,給不同的硬體加速元素新增一個不同的z-index屬性可以解決。-webkit-overflow-scrolling: touch使ios滾動順滑。
    // 初始化BScroll虛擬碼,生產慎用:

    ``` import BScroll from 'better-scroll'; this.myScroll = new BScroll('#wrapper', { mouseWheel : true, // 無需scrollbar scrollbar : false, // propType屬性設定為3在慣性動畫期間也觸發onscroll事件 probeType : 3, // 允許滾動列表內可點選、touch click : true, tap : true, // 上拉載入,正值自動觸發載入 pullUpLoad : { threshold: 50 } }); ```

一開始,我將better-scroll初始化程式碼放在container元件的componentDidMount函式中,但由於初始資料也在這個函式獲取,導致當返回較慢的時候初始化的#wrapper沒有內容,此時需要手動點選載入更多才展現資料,不符合預期。所以考慮將初始化程式碼放到list元件渲染完成之後的componentDidUpdate函式中。list元件渲染完成後,就可以初始化我們的滾動類,這裡使用的better-scroll,iscroll使用類似。具體參考上面連結。


    #wrapper {
        position:absolute;
        top:0;
        left:0;
        width:100vw;
        overflow:auto;
        height: 100vh;
        transform: translateZ(0);
        z-index: 33;
        -webkit-overflow-scrolling: touch
    }

具體的,可以將初始化程式碼放在list組建的container元件的handleScrollRefresh函式。這個函式作為props傳到list元件,在list元件的componentDidUpdate鉤子裡面執行:
container元件:


handleScrollRefresh () {
      if (this.myScroll) {
          this.myScroll.refresh();
          console.log('refreshed ');
      } else {
          console.log('initialized');
          this.myScroll = new BScroll('#wrapper', {
              ...//初始化引數
          });
          this.myScroll.on('scroll',this.handleScroll, 10);
          this.myScroll.on('pullingUp', this.loadMore);
      }
  }

list 元件:


componentDidUpdate () {
      if (this.props.onRefresh) {
          this.props.onRefresh();
      }
  }

網上很多滾動卡頓的情況,大都是載入資料後沒有執行refresh導致的。同時,載入資料成功後我們需要呼叫scroll的finishPullUp方法。下次上拉才能繼續載入資料。這樣,每當載入新的資料後,list元件就會執行componentDidUpdate,此時就呼叫了scroll的finishPullUp、refresh函式,使用起來無比順滑。

三、優化

  1. 和大多數滾動處理一樣,better-scroll的scroll事件也會頻繁觸發,這對效能還是有一定影響的,畢竟我們不需要過於頻繁的執行回撥函式。

    ``` throttle (func, delay) { let lastTime = null; return function () { let context = this; let args = arguments; let now = new Date().getTime(); if (!lastTime || (now - lastTime) > delay) { lastTime = now; func.apply(context, args); } }; }; ```

    不想寫直接使用lodash也可以:

    ``` //不精準的每秒十次 this.myScroll.on('scroll', this.throttle(this.handleScroll, 100)); ```
  2. 函式繫結,不傳參的情況下在constructor中繫結this。而不是在render中使用this.xxx.bind(this)。
  3. list 圖片大小限制,本次由於部分列表item圖片過大,在安卓上導致黑屏的問題出現。排查了很久才發現這個問題。通過在圖片url拼接引數限制大小解決了這個問題。

最後

感覺寫得好亂,做事情和寫文章果然是兩回事。。。
有興趣可以訪問:https://3hours.taobao.com/new...
一起來做公益吧!

來源:https://segmentfault.com/a/1190000017520404