如何用小程式實現類原生APP下一條無限刷體驗

TNFE發表於2019-06-27

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 作為執行環境。在架構上,WebViewJavascriptCore 都是獨立的模組,並不具備資料直接共享的通道。當前,檢視層和邏輯層的資料傳輸,實際上通過兩邊提供的 evaluateJavascript 所實現。即使用者傳輸的資料,需要將其轉換為字串形式傳遞,同時把轉換後的資料內容拼接成一份 JS 指令碼,再通過執行 JS 指令碼的形式傳遞到兩邊獨立環境。

evaluateJavascript 的執行會受很多方面的影響,資料到達檢視層並不是實時的。

  • 每次 setData 的呼叫都是一次程式間通訊過程,通訊開銷與 setData 的資料量正相關。
  • setData 會引發檢視層頁面內容的更新,這一耗時操作一定時間中會阻塞使用者互動。
  • setData 是小程式開發中使用最頻繁的介面,也是最容易引發效能問題的介面。

避免不當使用setData

  • data 應僅包括與頁面渲染相關的資料,其他資料可繫結在this上。使用 data 在方法間共享資料,會增加 setData 傳輸的資料量,。
  • 使用 setData 傳輸大量資料,通訊耗時與資料正相關,頁面更新延遲可能造成頁面更新開銷增加。僅傳輸頁面中發生變化的資料,使用 setData 的特殊 key 實現區域性更新。
  • 避免不必要的 setData,避免短時間內頻繁呼叫 setData,對連續的setData呼叫進行合併。不然會導致操作卡頓,互動延遲,阻塞通訊,頁面渲染延遲。
  • 避免在後臺頁面進行 setData,這樣會搶佔前臺頁面的渲染資源。可將頁面切入後臺後的setData呼叫延遲到頁面重新展示時執行。

優化示例

無限上拉重新整理的資料會採用分頁介面的形式,分多次請求回來。在使用分頁介面拉取到下一刷的資料後,我們需要呼叫setData將資料寫進dataarticleData中,這個articleData是一個陣列,裡面存放著所有的文章資料,資料量十分龐大,如果直接setData會增加通訊耗時和頁面更新開銷,導致操作卡頓,互動延遲。

為了避免這個問題,我們將articleData改進為一個二維陣列,每一次setData通過分頁的 cachedCount 標識來實現區域性更新,具體程式碼如下:

this.setData({
  [`articleData[${cachedCount}]`]: [...data],
  cachedCount: cachedCount + 1,
})

articleData的結構如下:

4.3 體驗優化

解決了操作卡頓,互動延遲等問題,我們還需要對動畫和互動的體驗進行優化,以達到類原生APP效果的體驗。

在文章間上拉切換時,我們使用了<swiper>元件自帶的動畫效果,並通過設定durationeasing-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

相關文章