快速实现上滑加载更多

实现方式

在智能小程序的开发过程中,经常会遇到页面列表数量较多的情况,此时可以通过【分页】加载数据,并监听页面滑动到底部时触发【上滑加载更多】,从而增加页面首屏渲染速度。 想要实现这种分页展示数据,上滑加载更多的效果,主要有以下几种方式:

  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 是否滚动
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章