一次react滾動列表的實踐---相容ios安卓
一、背景
近期專案改版,對原有的h5頁面進行了重新設計,資料呈現變成了瀑布流。希望新版相容ios和安卓兩端的情況下,無限制的重新整理載入資料。大致效果如下:
整個頁面分4部分:
- 頂部導航
- 步數狀態卡片
- 使用者資訊卡片
- 滾動列表
期望效果:列表滾動到使用者資訊卡片消失後,展示另一個吸頂的導航欄。
效果如下:
分析可以發現,首先我們要做的就是適配iPhone X,其次我們需要監聽列表的滾動高度,在pc和安卓上監聽滾動事件是沒有問題的,但是ios上滾動過程中不會觸發scroll事件,而是滾動結束後會觸發onscrollend事件,這就不能滿足實時監聽高度的要求。經過簡單調研,決定站在巨人的肩膀上,通過
二、進入正題
- 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); }
-
2.頁面
頁面結構不多說,比較基礎。
``` 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 } }); ```
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虛擬碼,生產慎用:
一開始,我將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函式,使用起來無比順滑。
三、優化
-
和大多數滾動處理一樣,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)); ``` - 函式繫結,不傳參的情況下在constructor中繫結this。而不是在render中使用this.xxx.bind(this)。
- list 圖片大小限制,本次由於部分列表item圖片過大,在安卓上導致黑屏的問題出現。排查了很久才發現這個問題。通過在圖片url拼接引數限制大小解決了這個問題。
最後
感覺寫得好亂,做事情和寫文章果然是兩回事。。。
有興趣可以訪問:https://3hours.taobao.com/new...
一起來做公益吧!