下拉刷新跟触底加载

小程序实现触底加载跟下拉刷新

下拉刷新和触底加载的使用场景以及业务需求

  1. 下拉刷新
    1. 主要应用于项目的首页或者需要实时数据的场景
    2. 业务需求
      1. 提供实时数据(避免数据更新后仍显示旧数据的问题)
      2. 网络错误的恢复
      3. 给用户一个主动刷新的方式
      4. 避免强制刷新导致的用户体验感不好
  2. 触底加载
    1. 主要应用于项目当中的长列表(比如猜你喜欢,多tabs推荐列表,订单列表)
    2. 业务需求
      1. 减少首屏加载时间(优化性能,避免一次加载过多导致卡顿)
      2. 提高用户的体验感(不需要用户去手动的触发加载更多数据)

实现下拉刷新和触底加载的思路

下拉刷新实现

(1)监听用户下拉动作
通过小程序提供的onPullDownRefresh生命周期或scroll-viewrefresherrefresh属性触发刷新逻辑。

  • 如何区分用户主动下拉还是页面的初始化
    • 使用onLoad生命周期存储页面初始化的标记信息,比如设置一个boolean值为true,用户触发下拉刷新时重置数据并改变初始化信息为false

(2)触发数据加载

调用API获取最新数据,清空旧数据列表,然后在追加数据。

  • Api请求失败或超时的处理方法
    • 使用toast进行错误提示并通过wx.stopPullDownRefresh停止动画

(3)停止刷新动画

加载完成后使用wx.stopPullDownRefresh停止动画。

  • 数据加载慢导致动画卡顿的处理方法
    • 设置定时器强制停止动画(例如5秒),避免长时间卡顿。

触底加载实现

(1)监听滚动事件
通过scroll-viewscrolltolower事件或onReachBottom生命周期监听滚动触底

  • 如何避免频繁触发触底事件
    • 添加防抖或节流逻辑(如300毫秒延迟),避免频繁触发请求。

(2)触发数据加载

分页加载需维护当前页码,每次触底递增页码并请求对应数据。

  • 如何判断没有更多数据
    • 根据API返回的total字段或空数组用于判断有无更多数据,设置标志停止后续请求。

(3)将新数据追加到列表

  • 数据错位或重复
    • 根据返回的数据按照时间倒序排列,前端按照顺序添加

关键问题与优化方案

数据同步与性能
下拉刷新时,若数据量过大可能导致渲染延迟。建议优先加载首屏关键数据,非关键数据异步处理。触底加载的分页大小需根据接口性能动态调整(如初始10条,后续20条)。

错误处理与用户体验
网络请求失败时,除Toast提示外,可提供手动重试按钮。触底加载无更多数据时,显示友好提示(如“已加载全部内容”)。列表渲染使用key属性确保数据更新效率,避免重复渲染。


代码结构示例

下拉刷新

<script setup lang="ts">
//下拉刷新状态
const isTriggered =ref(false)
// 自定义下拉刷新被触发
const onRefresherrefresh=async()=>{
// 开启动画
isTriggered.value = true
//重置猜你喜欢组件数据
guessRef.value?.resetData()//加载数据
await Promise.all([其他数据加载完成])
isTriggered.value = false
</script>

<scrol1-view
refresher-enabled
@refresherrefresh="onRefresherrefresh":refresher-triggered="isTriggered"
class="scrol1-view"
scro11-y>
..省略
</scrol1-view>

触底加载(分页加载)

const goodsList = ref([])
//已结束标记
const finish = ref(false)
//获取数据
const getGoodsData=async()=>{
//退出分页判断
if(finish.value === true){
return uni.showToast({ icon:'none',title:'没有更多数据~'})
const res = await getGoodsAPI(pageParams)
// 数组追加
guessList.value.push(...res.result.items)
// 分页条件
if(pageParams.page<res.result.pages){
// 页码累加
pageParams.page++
}else {
finish.value = true
// 重置数据
const resetData=()=>{
pageParams.page =1guessList.value=[]
finish.value = false



const isTriggered =ref(false)
// 自定义下拉刷新被触发
const onRefresherrefresh=async()=>{
// 开启动画
isTriggered.value = true
// 重置数据
goodsRef.value?.resetData()
await Promise.all([获取数据的逻辑])
isTriggered.value = false
}


注意事项

  • 动画流畅性:下拉刷新动画时长应匹配数据加载时间,可设置最小显示时长(如1秒)避免闪烁。
  • 数据一致性:分页加载时,确保后端数据排序稳定(如按创建时间降序),前端严格按顺序追加。
  • 内存管理:超长列表需结合虚拟滚动技术(如recycle-view),防止DOM节点过多导致性能下降。

 原生Js实现下拉刷新和触底加载

       下拉刷新

  1. 监听触摸事件
    1. touchstart:记录触摸开始的位置。
    2. touchmove:计算触摸移动的距离,判断是否达到下拉刷新的阈值。
    3. touchend:在触摸结束时,如果达到阈值,则触发刷新操作。
  2. 添加刷新指示器: 在页面顶部添加一个刷新指示器(如旋转的图标或进度条),在触发下拉刷新时显示。
  3. 执行刷新操作:在达到下拉刷新阈值后,执行实际的刷新操作(如重新加载数据)。 刷新完成后,隐藏刷新指示器。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>下拉刷新示例</title>
  <style>
    body, html {
      margin: 0;
      padding: 0;
      height: 100%;
      overflow: hidden;
    }
    .refresh-indicator {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 50px;
      background-color: #f0f0f0;
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      transition: opacity 0.3s;
    }
    .refresh-indicator.active {
      opacity: 1;
    }
    .content {
      height: 100%;
      overflow-y: scroll;
      -webkit-overflow-scrolling: touch;
    }
  </style>
</head>
<body>
  <div class="refresh-indicator">下拉刷新...</div>
  <div class="content">
    <!-- 页面内容 -->
    <h1>下拉刷新示例</h1>
    <p>这里是页面内容...</p>
  </div>

  <script>
    const refreshIndicator = document.querySelector('.refresh-indicator');
    const content = document.querySelector('.content');
    let startY = 0;
    let distance = 0;
    const threshold = 50; // 下拉刷新的阈值

    content.addEventListener('touchstart', (e) => {
      startY = e.touches[0].clientY;
    });

    content.addEventListener('touchmove', (e) => {
      const currentY = e.touches[0].clientY;
      distance = currentY - startY;

      if (distance > 0 && distance <= threshold) {
        refreshIndicator.style.transform = `translateY(${distance}px)`;
        refreshIndicator.style.opacity = distance / threshold;
      }
    });

    content.addEventListener('touchend', () => {
      if (distance > threshold) {
        // 触发刷新操作
        refreshIndicator.classList.add('active');
        refreshData().then(() => {
          // 刷新完成后隐藏刷新指示器
          refreshIndicator.classList.remove('active');
          // 重置位置
          refreshIndicator.style.transform = '';
          refreshIndicator.style.opacity = '';
          distance = 0;
        });
      } else {
        // 未达到阈值,重置位置
        refreshIndicator.style.transform = '';
        refreshIndicator.style.opacity = '';
        distance = 0;
      }
    });

    function refreshData() {
      return new Promise((resolve) => {
        // 模拟刷新操作
        setTimeout(() => {
          console.log('数据已刷新');
          resolve();
        }, 2000);
      });
    }
  </script>
</body>
</html>

触底加载(分页加载)

  1. 监听滚动事件: 监听 scroll 事件,计算当前滚动位置是否接近页面底部。
  2. 触发加载操作: 当滚动位置接近页面底部时,触发加载更多数据的操作。
  3. 显示加载状态: 在加载过程中,显示加载指示器(如旋转的图标或进度条)。
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>触底加载示例</title>
      <style>
        body, html {
          margin: 0;
          padding: 0;
          height: 100%;
          overflow: hidden;
        }
        .content {
          height: 100%;
          overflow-y: scroll;
          -webkit-overflow-scrolling: touch;
        }
        .item {
          height: 100px;
          line-height: 100px;
          text-align: center;
          border-bottom: 1px solid #ccc;
        }
        .loading-indicator {
          text-align: center;
          padding: 20px;
          display: none;
        }
        .loading-indicator.active {
          display: block;
        }
      </style>
    </head>
    <body>
      <div class="content">
        <!-- 页面内容 -->
        <div class="item">Item 1</div>
        <div class="item">Item 2</div>
        <div class="item">Item 3</div>
        <!-- 更多项 -->
        <div class="loading-indicator">加载中...</div>
      </div>
    
      <script>
        const content = document.querySelector('.content');
        const loadingIndicator = document.querySelector('.loading-indicator');
        let currentPage = 1;
        const pageSize = 10;
    
        function loadMoreItems() {
          return new Promise((resolve) => {
            // 模拟加载更多数据
            setTimeout(() => {
              for (let i = 0; i < pageSize; i++) {
                const item = document.createElement('div');
                item.className = 'item';
                item.textContent = `Item ${currentPage * pageSize + i + 1}`;
                content.appendChild(item);
              }
              currentPage++;
              resolve();
            }, 2000);
          });
        }
    
        content.addEventListener('scroll', () => {
          const scrollTop = content.scrollTop;
          const scrollHeight = content.scrollHeight;
          const clientHeight = content.clientHeight;
    
          if (scrollTop + clientHeight >= scrollHeight - 5) {
            // 触发加载更多数据
            loadingIndicator.classList.add('active');
            loadMoreItems().then(() => {
              loadingIndicator.classList.remove('active');
            });
          }
        });
      </script>
    </body>
    </html>
    

    长列表渲染优化的另一种方式

虚拟列表

虚拟列表(Virtual List)是为了优化长列表的性能,通过仅渲染可视区域内的元素,减少DOM节点数量,从而提高渲染效率和用户体验,也可以用于实现无限滚动

vue中实现虚拟列表

使用第三方库vue-virtual-scroll-list

<template>
  <virtual-list
    :size="50" <!-- 每个列表项的高度 -->
    :remain="10" <!-- 可视区域内保留的项数 -->
    :data-key="'id'"
    :data-sources="items"
    :data-component="ListItem"
  />
</template>

<script>
import VirtualList from 'vue-virtual-scroll-list';
import ListItem from './ListItem.vue'; // 自定义的列表项组件

export default {
  components: {
    VirtualList,
    ListItem
  },
  data() {
    return {
      items: Array.from({ length: 1000 }, (_, index) => ({ id: index, content: `Item ${index}` }))
    };
  }
};
</script>
<template>
  <div class="list-item">
    {{ item.content }}
  </div>
</template>

<script>
export default {
  props: {
    item: Object
  }
};
</script>

<style>
.list-item {
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #eee;
}
</style>

react中实现虚拟列表

React中实现虚拟列表同样可以使用第三方库如react-windowreact-virtualized

import React from 'react';
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div className={`list-item ${index % 2 ? 'odd' : 'even'}`} style={style}>
    Item {index}
  </div>
);

const VirtualList = () => {
  const itemCount = 1000;
  const itemSize = 50;

  return (
    <List
      height={800}
      itemCount={itemCount}
      itemSize={itemSize}
      width={300}
    >
      {Row}
    </List>
  );
};

export default VirtualList;

第三方组件库

有些第三方组件库已经实现了下拉刷新或虚拟列表,触底加载等逻辑,可以直接使用,例如vant,antd-mobile等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值