快速實現上滑加載更多

實現方式

在智能小程序的開發過程中,經常會遇到頁面列表數量較多的情況,此時可以通過【分頁】加載數據,並監聽頁面滑動到底部時觸發【上滑加載更多】,從而增加頁面首屏渲染速度。 想要實現這種分頁展示數據,上滑加載更多的效果,主要有以下幾種方式:

  1. 使用 view自定義信息流組件 + onReachBottom
  2. 使用 scroll-view + bindscrolltolower
  3. 使用 smart-ui 中的feed信息流組件
  4. 使用 swiper + scroll-view 自定義信息流組件 + bindscrolltolower
  5. 使用 swiper + view 自定義信息流組件 + onReachBottom
  6. 使用 page-feed信息流模版

以下是具體方案中會使用到的組件或api:

組件/ api名稱 描述 類型
scroll-view 滾動區域組件 原生組件
view 視圖組件 原生組件
tabs 標籤欄組件 原生組件
tabs-item 標籤欄子項組件 原生組件
swiper 滑塊視圖容器 原生組件
swiper-item 滑塊視圖子項組件 原生組件
feed 信息流組件 擴展組件(smart-ui
spin 加載狀態組件 擴展組件(smart-ui)
page-status 頁面狀態組件 擴展組件(smart-ui)
page-feed 信息流模版 頁面模版
createSelectorQuery 返回一個 SelectorQuery 對象實例 swan api
onReachBottom 頁面加載到底部時觸發 頁面事件函數

針對不同的場景,可以使用不同的方式實現,下面我從四個常見的場景進行具體分析。

分場景描述

【場景一】

  1. 場景特徵:頁面整體由一塊滾動區域(A) + 上滑加載更多(B)組成
    圖片
  2. 場景展示:
  3. 實現方案:

方案一:採用 page-feed 信息流模版實現

用百度開發者工具打開
關鍵代碼:

<!-- feed 信息流 組件 -->
<smt-feed
    s-if="!showPageStatus"
    class="smt-feed pull-down-refresh"
    pull-to-refresh
    bind:refresh="onRefresh"
    bind:scrolltolower="scrollToLower"
    bind:scroll="scrollHandler"
    text="{{text}}"
>
    <!-- feed 信息流子組件 -->
    <smt-feed-item 
        s-for="item in list"
        theme="{{item.theme}}"
        content="{{item.content}}" 
        video="{{item.video}}"
        status="{{item.status}}"
        bindfeeditemtap="feedItemTap"
    >
    </smt-feed-item>
    <!-- 上滑加載更多組件 -->
    <smt-spin s-if="loaded" status="{{status}}" bind:tap="reload"></smt-spin>
</smt-feed>
<!-- 頁面狀態組件 -->
 <smt-page-status
    s-if="showPageStatus"
    class="content-loading"
    icon="{{loadingIcon}}"
    loading="{{loading}}"
    showBtn="{{loadingBtn}}"
    title="{{loadingTitle}}"
    loadingTitle="正在加載..."
    bind:smtreloading="reloadPage">
</smt-page-status>

方案優勢:
拿來即用,避免重複造輪子。npm i @smt-ui-template/page-feed 直接安裝就可以使用,涵蓋了【信息流圖片懶加載】【下拉刷新頁面】【上滑加載更多】【無網絡、無內容、頁面加載中狀態頁】等功能。

方案二:採用 view 自定義信息流組件 + onReachBottom

用百度開發者工具打開
關鍵代碼:
1)配置頁面高度爲整個視口高度

<!-- 需要配置頁面高度爲整個視口高度 -->
<view class="page-container" style="height: 100vh">
    <!-- 自定義信息流組件 -->
    <list list="{{list}}"></list>
    <!-- 自定義上滑加載更多組件 -->
    <loading-more status="{{status}}" bind:tap="reload"></loading-more>
</view>

2)頁面 onLoad 時獲取首屏數據

 data: {
    // 初始化頁碼爲0,表示第一頁
    count: 0
},

onLoad() {
    this.fetchFirstPageData();
},

/**
 * 渲染首屏數據
 */
async fetchFirstPageData() {
    // 請求首頁數據
    const {data: list} = await this.fetchData();
    this.syncSetData(this, {
        // 渲染信息流組件數據
        list,
        // 頁碼+1,用於請求下一屏數據時傳遞頁碼
        count: this.data.count + 1,
        // 加載狀態組件,status: 1表示“加載中”
        status: 1
    });
}

3)監聽頁面onReachBottom時觸發加載更多內容

/**
 * 監聽頁面滾動到底部,觸發加載更多
 */
onReachBottom() {
    this.scrollToLower();
},
/**
 * 上拉觸底加載更多
 */
async scrollToLower() {
    const {data: list} = await this.fetchData();
    // 模擬請求第四頁數據時,加載組件展示“加載異常”
    const fail = this.data.count === 3;
    // 模擬請求第五頁數據時,加載組件展示“沒有更多內容”
    const end = this.data.count === 5;
    if (fail || end) {
        await this.syncSetData(this, {
            // status: 3,加載組件展示“加載失敗,請點擊重新加載”
            // status: 2,加載組件展示“已經到底啦“
            status: fail ? 3 : 2
        });
        return;
    }
    await this.syncSetData(this, {
        // 當前數據與下一頁數據進行拼接
        list: list.concat(this.data.list || []),
        // 翻頁,頁碼+1
        count: ++this.data.count
    });
}

方案優勢:
1、自定義信息流組件和加載更多組件,可針對具體需求靈活處理
2、無三方組件引入,減少包體積

【場景二】

1、場景特徵:頁面整體由一塊位置固定的區域(A) + 一塊滾動區域(B) + 上滑加載更多(C)組成。
圖片
2、場景展示:
圖片
3、實現方案

方案一:採用 smart-ui 中的 feed 信息流組件 + spin 加載狀態組件

用百度開發者工具打開
關鍵代碼:
1)爲 feed 組件添加滾動區域的高度

<view class="page-container" >
     <!-- 固定區域 -->
    <view class="placeholder"></view>
    <!-- feed 組件 -->
    <smt-feed
        <!-- 定義 pull-down-refresh 類,用於獲取組件實例-->
        class="smt-feed pull-down-refresh"
        <!-- 下拉刷新關鍵字 -->
        pull-to-refresh
        <!-- 手動觸發下拉刷新操作-->
        bind:refresh="onRefresh"
        <!-- 監聽頁面滾動到底部 -->
        bind:scrolltolower="scrollToLower"
        text="{{text}}"
    >
        <!-- 自定義信息流組件 -->
        <list list="{{list}}"></list>
        <!-- 加載狀態組件 -->
        <smt-spin status="{{status}}" bind:tap="reload"></smt-spin>
    </smt-feed>
</view>
/* 固定區域樣式 */
.placeholder {
    height: 1.52rem;
    background-color: #e0e0e0;
}
/* feed 組件樣式*/
.smt-feed {
    display: block;
    /* 滾動區域高度需要 - 固定區域高度(1.52rem)*/
    height: calc(100vh - 1.52rem);
    background: #fff;
}

2)初始化頁面數據

data: {
    // 初始化加載狀態組件,默認展示“加載中”
    status: 1,
    // 初始化信息流組件數據
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
    // 初始化頁面數據,默認頁碼爲0
    count: 0
}

3)實現 feed下拉刷新效果,如需要實現自動或手動執行下拉刷新效果,需在組件上添加 pull-to-refresh 關鍵字

<view class="page-container" >
     <!-- 固定區域 -->
    <view class="placeholder"></view>
    <!-- feed 組件 -->
    <smt-feed
        <!-- 定義 pull-down-refresh 類,用於獲取組件實例-->
        class="smt-feed pull-down-refresh"
        <!-- 下拉刷新關鍵字 -->
        pull-to-refresh
        <!-- 手動觸發下拉刷新操作-->
        bind:refresh="onRefresh"
        <!-- 監聽頁面滾動到底部 -->
        bind:scrolltolower="scrollToLower"
        text="{{text}}"
    >
        <!-- 自定義信息流組件 -->
        <list list="{{list}}"></list>
        <!-- 加載狀態組件 -->
        <smt-spin status="{{status}}" bind:tap="reload"></smt-spin>
    </smt-feed>
</view>

4)進入頁面時自動刷新

onShow() {
    this.onRefresh();
}

5)手動下拉刷新

/**
 * 下拉更新最新內容
 */
async onRefresh() {
    // 1、獲取組件實例
    const refresh = await this.selectComponent('.pull-down-refresh');
    // 2、調用組件上的方法模擬下拉刷新
    refresh.startRefresh();
    // 3、獲取接口數據並更新頁面內容
    const {data: list} = await this.fetchData();
    await syncSetData(this, {
        status: 1,
        count: 0,
        list: list || this.data.list,
        text: list ? `爲你推薦${list.length}條更新` : '暫時沒有更新,休息一下'
    });
    // 4、關閉刷新
    refresh.stopRefresh();
}

6)實現上滑加載更多內容

/**
 * 上拉觸底加載更多
 */
async scrollToLower() {
    const {data: list} = await this.fetchData();
    const fail = this.data.count === 3;
    const end = this.data.count === 5;
    if (fail || end) {
        this.setData({
            status: fail ? 3 : 2
        });
        return;
    }
    await syncSetData(this, {
        list: list.concat(this.data.list || []),
        count: ++this.data.count
    });
}

方案優勢:
1、使用feed組件,無需使用view自定義信息流組件,輕鬆實現下拉刷新頁面,頁面局部刷新等功能。
2、使用spin組件,無需使用view 自定義加載狀態組件,快速實現上滑加載更多功能。

方案二:採用 scroll-view 自定義信息流組件 + 自定義上滑加載更多組件

用百度開發者工具打開
關鍵代碼:
1)使用 scroll-vew 包裹滾動區域

<view class="page-container">
    <!-- 固定區域 -->
    <view class="placeholder"></view>
    <!-- feed 組件 -->
    <view class="feed-wrap">
        <!-- scroll-view 組件 -->
        <scroll-view class="scroll-wrap" scroll-y scroll-top="{= scrollTop =}" bindscrolltolower="fetchMoreData">
            <list list="{{list}}"></list>
            <loading-more status="{{status}}" bindtap="reload"></loading-more>
        </scroll-view>
    </view>
</view>

2)scroll-view 外層的 dom 的高度需要用整個頁面的高度減去固定區域的高度

/* 整個頁面高度爲100vh */
.page-container {
    height: 100vh;
}

/* 固定區域高度 */
.placeholder {
    height: 1.52rem;
    background-color: #e0e0e0;
}

/* scroll-view 外層高度需要整個頁面高度 - 固定區域高度 */
.feed-wrap {
    position: relative;
    height: calc(100% - 1.52rem);
    overflow: hidden;
}
/* scroll-view 高度 */
.scroll-wrap {
    height: 100%;
}

3)通過scroll-view 上綁定的bindscrolltolower 方法,展示加載更多,並獲取下一頁數據,具體方法可參考【方案一-> 關鍵代碼6】
4)自定義loading-more 組件,實現下拉加載更多效果

// 傳入的 status 爲對應下標,組件展示對應文案
textConfig: {
    type: Array,
    value: ['點擊加載更多', '正在加載...', '已經到底啦', '加載失敗 點擊重新加載']
}

方案優勢:
1、scroll-view組件是小程序原生組件,方式靈活,使用於頁面任意滾動區域。
2、自定義 loading-more 組件,可針對具體需求實現組件樣式,同時可減少三方組件的引入,減少代碼體積。

【場景三】

1、場景特徵:整個頁面由 tabs 組件 (A) + swiper 組件(B)+ 自定義信息流組件(C) + 自定義加載更多組件(D) 圖片
2、場景展示:
圖片
3、實現方案:

方案一:採用 tabs 組件 + scroll-view 自定義信息流組件+ swiper 組件

用百度開發者工具打開
關鍵代碼:
1)頁面結構

<view class="page-container">
    <!-- tabs 多標籤組件 -->
    <tabs
        class="tabs-wrap"
        active-name="{{activeTabName}}"
        class="border-bottom"
        tabs-background-color="#fff"
        tabs-underline-color="#000"
        tabs-inactive-text-color="#666"
        tabs-active-text-color="#000"
        bindtabchange="handleTabChange"
    >
        <tab-item
            s-for="tab in tabs"
            name="{{tab.name}}"
            label="{{tab.label}}"
        />
    </tabs>
    <view class="feed-container">
        <!-- swiper 滑塊組件,可左右滑動展示區域 -->
        <swiper
            class="swiper"
            current="{{activeSwiperId}}"
            bindchange="handleSwiperChange">
            <!-- swiper-item 滑塊內部組件,包裹了內部滾動區域 -->
            <swiper-item
                item-id="{{activeSwiperId}}"
                s-for="data in swiperData">
                <!-- 內部可滾動區域 -->
                <scroll-view
                    class="swiper-scroll-view"
                    scroll-y
                    bindscrolltolower="fetchMorePageData">
                    <list list="{{data}}"></list>
                    <view class="loading-more-wrap">
                        <loading-more status="{{status}}" bindtap="reload" secureBottom="false"></loading-more>
                    </view>
                </scroll-view>
            </swiper-item>
        </swiper>
    </view>
</view>

2)配置 swiper以及scroll-view 的高度

/* 頁面整個高度 */
.page-container {
    height: 100vh;
}
/* swiper 外部高度爲整個頁面高度 - tabs 組件高度 */
.feed-container {
    width: 100%;
    height: calc(100vh - 72.46rpx);
}

/* swiper 以及內部scroll-view 高度*/
.swiper,
.swiper-scroll-view {
    height: 100%;
}
  1. 通過 scroll-view 上綁定的 bindscrolltolower 方法,展示加載更多,並獲取下一頁數據。具體方式可參考【方案一->關鍵代碼6】。
    方案優勢:
    1、使用 swiper + scroll-view 實現信息流在 swiper-item 中滾動,無需動態計算信息流高度。
    2、切換 tab 時,能保留每個 tab 下用戶上次瀏覽的位置。

方案二:採用 tabs 組件 + view 自定義信息流組件 + swiper 組件

用百度開發者工具打開
關鍵代碼:
1)頁面結構

<!-- swiper 高度 swiperH 需要動態計算 -->
<swiper class="swiper" style="height: {{swiperH}}" current="{{currentTab}}" bindchange="swiperChange">
    <swiper-item class="item">
        <view class="goodsList">
            <view s-for="item,index in goods">
                <view class="goodsItem">
                    <view class="goodsImage">
                        <image bindload="imageLoad" src="{{item.img}}"></image>
                    </view>
                    <view class="goodsTitle">
                        <text>{{item.title}}</text>
                    </view>
                </view>
            </view>
        </view>
        <view class="loading">努力加載中...</view>
    </swiper-item>
</swiper>

2)動態計算 swiper 高度

// swiper-item 上的 class
swan.createSelectorQuery().selectAll('.item')
    .boundingClientRect(rect => {  
        this.setData({
            swiperH: rect[currentTab].height + 'px'
        });
}).exec();

3)swiper 以及 swiper-item 樣式配置

.swiper{
    min-height: 100vh;
    width: 100%;
}
.swiper swiper-item {
    /* ------- 需要注意這裏 ------- */ 
    height: auto !important;
    overflow: auto;
}

4)監聽頁面滑動到底部時,觸發 onReachBottom,執行加載更多操作

onReachBottom() {
    // 由於測試接口返回數據過快,爲了方便測試所以延遲一分鐘發送請求
    setTimeout(() => {
        this.initData();
    }, 1000);
}

【場景四】

1、場景特徵:一塊固定區域(A) + tabs 多標籤(B) + 展示區(C),其中展示區域(C)與 tabs 一起滾動,當滑動到頁面頂端時 tabs 吸頂,展示區域(C)內部滾動
圖片
2、場景展示:
圖片
3、實現方案:

方案一:採用 tabs 組件 + swiper 組件 + scroll-view 自定義信息流組件

用百度開發者工具打開
關鍵代碼:
1)tabs 組件定位爲 sticky,swiper 高度爲 100vh

.tabs-wrap {
    height: 50px;
    width: 100%;
    position: sticky;
    top: 0;
    z-index: 100;
}

.swiper {
    height: 100vh;
}

2)通過動態配置 scroll-view 的 scroll-y 屬性,實現信息流在 swiper-item 中滾動

<scroll-view
    <!-- 信息流初始狀態爲不滾動 -->
     scroll-y="{{enableScroll}}"
     bindscrolltolower="fetchMorePageData"
     bindscroll="onAppsScroll"
     style="height: 100%">
     <list list="{{list}}"></list>
     <loading-more status="{{status}}" bindtap="handleSpin"></loading-more>
 </scroll-view>

3)頁面獲取 tabs 數據後,計算 tabs 組件的位置

/**
 * 計算tabs的高度
 */
calTabsFixedTop() {
    // tabs-wrap 爲tabs 組件上綁定的class
    swan.createSelectorQuery()
        .select('.tabs-wrap')
        .boundingClientRect(res => {
            if (!res) {
                return;
            }
            this.setData({
                tabFixedTop: res.top
            });
        })
        .exec();
}

4)默認頁面監聽頁面滾動,當 tabs 組件滾動到頂部時,配置內容區域開始滾動: scroll-y 爲 true

/**
 * 監聽頁面滾動
 *
 * @param {Object} event 滾動事件對象
 */
onPageScroll(event) {
    const {fixedTabs, tabFixedTop} = this.data;
    const currentFixedTabs = event.scrollTop >= tabFixedTop;
    // 避免重複setData
    if (currentFixedTabs !== fixedTabs) {
        this.setData({
            // 當頁面滾動的高度大於tabs原來的高度時,說明tabs已經到頁面頂部,需要呈現吸頂態
            enableScroll: currentFixedTabs
        });
    }
}

總結

在上述的四種場景中,前兩個場景功能較爲簡單,基本通過 view + onReachBottom 或者 scroll-view + bindscrolltolower 配合實現,後兩個場景均屬於多 tab + swiper 場景,功能包含了 tab 切換以及內容區的左右橫滑等,功能較爲複雜,但也是較爲常用的信息流配合其他功能的組合打法,下面給出不同場景下,具體方案的優勢和劣勢,大家可以有針對性的選擇不同的實現方案。

場景 方案 優勢 劣勢
場景一 (頁面整體由一塊滾動區域 + 上滑加載更多組成) page-feed 信息流模版 拿來即用,快速實現信息流分頁加載 場景單一,整個頁面僅包含信息流和上滑加載更多
  view 自定義信息流組件 + onReachBottom 方式靈活,可針對具體需求靈活處理 信息流功能均需要自己開發,成本較高
場景二 (頁面整體由一塊位置固定的區域 + 一塊滾動區域 + 上滑加載更多組成) smart-ui 中的 feed 信息流組件 + 加載狀態組件 無需自定義信息流組件以及加載狀態組件 feed 組件包含了 scroll-view 組件,默認會出現區域滾動區域,不適用【信息流跟隨整個頁面一起滾動】此類需求
  scroll-view 自定義信息流組件 + 自定義上滑加載更多組件 方式靈活,可針對具體需求靈活處理,可配置 scroll-view 區域是否滾動 scroll-view 組件使用,需要參照官方文檔,按照組件要求開發
場景三 (整個頁面由 tabs + swiper + 自定義信息流組件 + 自定義加載更多組件組成) tabs 組件 + scroll-view 自定義信息流組件+ swiper 組件 無需動態計算信息流高度,能保留每個 tab 下用戶上次瀏覽的位置 1、使用到 swiper 組件多層級嵌套,樣式需要準確書寫<br>2、使用 swiper 需要一次性獲取所有 tabs 下的數據,並且對數據格式有一定的要求(二維數組)
  tabs 組件 + view 自定義信息流組件 + swiper 組件 方式靈活,可針對具體需求靈活處理 每次請求下一頁數據時,需要根據當前列表數據長度,動態計算 swiper 高度
場景四 (頁面整個由一塊固定區域 + tabs 多標籤 + 一塊展示區組成且tabs滾動到頁面頂部時吸頂,展示區開始滾動) tabs 組件 + swiper 組件 + scroll-view 自定義信息流組件 方式靈活,可適用大多數信息流分頁展示場景 需要監聽頁面滾動,動態計算 tabs 位置,來設置 scroll-view 是否滾動
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章