1.背景
如今資訊流業務是各大網際網路公司爭先搶佔的一個大面包,為了提高使用者的後續消費,產品想出了各種各樣的方法,例如在微視中,使用者可以無限上拉出下一條視訊;在知乎中,也可以無限上拉出下一條回答。這樣的操作方式使用者體驗更好,後續消費也更多。最近幾年的時間,微信小程式已經從一顆小小的萌芽成長為參天大樹,形成了較大規模的生態,小程式也擁有了一個很大的流量入口。
2.demo體驗
那如何才能在小程式中實現類原生APP效果的下一條無限刷體驗?
這篇文章詳細記錄了下一條無限刷效果的實現原理,以及細節和體驗優化,並將相關程式碼抽象成一個微信小程式程式碼片段,有需要的同學可檢視demo原始碼。
線上效果請用微信掃碼體驗:
小程式demo體驗請點選:https://developers.weixin.qq.com/s/vIfPUomP7f9a
3.實現原理
出於效能和相容性考慮,我們儘量採用小程式官方提供的原生元件來實現下一條無限刷效果。我們發現,可以將無限上拉下一篇的文章看作一個豎向滾動的輪播圖,又由於每一篇文章的內容長度高於一螢幕高度,所以需要實現文章內部可滾動,以及文章之間可以上拉和下拉切換的功能。
在多次嘗試後,我們最終採用了在<swiper>
元件內部巢狀一個<scroll-view>
元件的方式實現,利用<swiper>
元件來實現文章之間上拉和下拉切換的功能,利用<scroll-view>
來實現一篇文章內部可上下滾動的功能。
所以頁面的dom結構如下所示:
<swiper
class='scroll-swiper'
circular="{{false}}"
vertical="{{true}}"
bindchange="bindChange"
skip-hidden-item-layout="{{true}}"
duration="{{500}}"
easing-function="easeInCubic"
>
<block wx:for="{{articleData}}">
<swiper-item>
<scroll-view
scroll-top="0"
scroll-with-animation="{{false}}"
scroll-y
>
content
</scroll-view>
</swiper-item>
</block>
</swiper>
4.效能優化
我們知道view部分是執行在webview上的,所以前端領域的大多數優化方式都有用。例如減少程式碼包體積,使用分包,渲染效能優化等。下面主要講一下渲染效能優化。
4.1 dom優化
由於頁面需要無限上拉重新整理,所以要在<swiper>
元件中不斷的增加<swiper-item>
,這樣必然會導致頁面的dom節點成倍數的增加,最後非常卡頓。
為了優化頁面的dom節點,我們利用<swiper>
的current
和<swiper-item>
的index
來做優化,控制是否渲染dom節點。首先,僅當index <= current + 1
時渲染<swiper-item>
,也就是頁面中最多預先載入出下一條,而不是將介面返回的所有後續資料都渲染出來;其次,對於使用者已經消費過的之前的<swiper-item>
,不能直接銷燬dom節點,否則會導致<swiper>
的current
值出現錯亂,但是我們可以控制是否渲染<swiper-item>
內部的子節點,我們設定了僅當current <= index + 1 && index -1 <= current
時才會渲染<swiper-item>
中的內容,也就是僅渲染當先文章,及上一篇和下一篇的文章內容,其他文章的dom節點都被銷燬了。
這樣,無論使用者上拉重新整理了多少次,頁面中最多隻會渲染3篇文章的內容,避免了因為上拉次數太多導致的頁面卡頓。
4.2 分頁時setData的優化
setData工作原理
小程式的檢視層目前使用WebView
作為渲染載體,而邏輯層是由獨立的 JavascriptCore
作為執行環境。在架構上,WebView
和 JavascriptCore
都是獨立的模組,並不具備資料直接共享的通道。當前,檢視層和邏輯層的資料傳輸,實際上通過兩邊提供的 evaluateJavascript
所實現。即使用者傳輸的資料,需要將其轉換為字串形式傳遞,同時把轉換後的資料內容拼接成一份 JS
指令碼,再通過執行 JS
指令碼的形式傳遞到兩邊獨立環境。
而 evaluateJavascript
的執行會受很多方面的影響,資料到達檢視層並不是實時的。
- 每次
setData
的呼叫都是一次程式間通訊過程,通訊開銷與 setData 的資料量正相關。 -
setData
會引發檢視層頁面內容的更新,這一耗時操作一定時間中會阻塞使用者互動。 -
setData
是小程式開發中使用最頻繁的介面,也是最容易引發效能問題的介面。
避免不當使用setData
-
data
應僅包括與頁面渲染相關的資料,其他資料可繫結在this上。使用data
在方法間共享資料,會增加 setData 傳輸的資料量,。 - 使用
setData
傳輸大量資料,通訊耗時與資料正相關,頁面更新延遲可能造成頁面更新開銷增加。僅傳輸頁面中發生變化的資料,使用setData
的特殊key
實現區域性更新。 - 避免不必要的
setData
,避免短時間內頻繁呼叫setData
,對連續的setData呼叫進行合併。不然會導致操作卡頓,互動延遲,阻塞通訊,頁面渲染延遲。 - 避免在後臺頁面進行
setData
,這樣會搶佔前臺頁面的渲染資源。可將頁面切入後臺後的setData
呼叫延遲到頁面重新展示時執行。
優化示例
無限上拉重新整理的資料會採用分頁介面的形式,分多次請求回來。在使用分頁介面拉取到下一刷的資料後,我們需要呼叫setData
將資料寫進data
的articleData
中,這個articleData
是一個陣列,裡面存放著所有的文章資料,資料量十分龐大,如果直接setData
會增加通訊耗時和頁面更新開銷,導致操作卡頓,互動延遲。
為了避免這個問題,我們將articleData
改進為一個二維陣列,每一次setData
通過分頁的 cachedCount
標識來實現區域性更新,具體程式碼如下:
this.setData({
[`articleData[${cachedCount}]`]: [...data],
cachedCount: cachedCount + 1,
})
articleData
的結構如下:
4.3 體驗優化
解決了操作卡頓,互動延遲等問題,我們還需要對動畫和互動的體驗進行優化,以達到類原生APP效果的體驗。
在文章間上拉切換時,我們使用了<swiper>
元件自帶的動畫效果,並通過設定duration
和easing-function
來優化滾動細節和動畫。
當使用者閱讀文章到底部時,會提示下一篇文章的標題等資訊,而在頁面上拉時,由於下一篇文章的內容已經載入出來了,這樣在滑動過程中會出現兩個重複的標題。為了避免這種情況出現,我們通過一個佔滿螢幕寬高的空白<view>
來將下一篇文章的內容撐出螢幕,並在滾動結束時,通過hidden="{{index !== current && index !== current + 1}}"
來隱藏這個空白<view>
,並對這個空白<view>
的高度變化增加動畫,來實現下一篇文章從螢幕底部滾動到螢幕頂部的效果:
.fake-scroll {
height: 100%;
width: 100%;
transition: height 0.3s cubic-bezier(0.167,0.167,0.4,1);
}
而當使用者想要上拉檢視之前閱讀過的文章時,我們需要給使用者一個“下滑檢視上一條”提示,所以也可以採用同上的方式,通過一個佔滿螢幕寬高的提示語<view>
來將上一篇文章的內容撐出螢幕,並在滾動結束時,通過wx:if="{{index + 1 === current}}"
來隱藏這個提示語<view>
,並對這個提示語<view>
的透明度變化增加動畫,來實現下拉時提示“下滑檢視上一條”的效果:
.fake-previous {
height: 100%;
width: 100%;
opacity: 0;
transition: opacity 1s ease-in;
}
.fake-previous.show-fake-previous {
opacity: 1;
}
至此,這個類原生APP效果的下一條無限刷體驗的需求的所有要點和細節都已實現。
記錄在此,歡迎交流和討論。
小程式demo體驗請點選:https://developers.weixin.qq.com/s/vIfPUomP7f9a