移动端下拉刷新的实现

前言

下拉刷新的原理: 实际上就是创建一个Pull组件 ,用其包裹可下拉部分,并在组件内(也就是被包裹的下拉部分的上边)加上下拉后需要展示出来的部分(头部),不下拉则隐藏,再绑定触摸事件 ,获取用户手指移动的起始y轴坐标,移动过程中的y轴坐标,从而获得手指拉下的距离,再利用幂函数 实现容器下拉距离与手指下拉距离的非线性关系 ,接着控制容器下拉最大距离,以及通过容器下拉距离控制展示的字样,如开始下拉时展示"下拉刷新... ",下拉到一定距离时展示"释放加载...",最后设置一个阈值 ,当松手之后,则将显示文本改为"加载中..." ,容器收缩到该阈值,并保持不变,同时再次向后端发送接口请求,当前端接收到请求回来的数据后,整个头部则完全收回,同时将文本改为"下拉刷新..."。

下拉刷新的实现

使用的是react框架,我需要使用下拉刷新的文件为NoteList/index.js,所以需要将创建的Pull组件拿到该文件下用,使用其包裹要下拉的部分即可

1. 创建Pull组件

  • 创建一个Pull文件夹,并在其内创建index.jsx文件

  • 需要接收一个children参数,因为该组件内部是需要放下拉的内容的

  • onLoad参数,为父组件内发送请求的函数,因为手指松开后需要重新发送请求,所以需要重新调用该函数

  • 还需要finished, setFinished两个参数,都是父组件传过来的,finished表示是否刷新完毕的状态,父组件的onLoad函数内,在请求发送完毕并收到数据后便更新finished的值为true,子组件收到后则可使用setFinished将值重新设为false,为下一次刷新做准备

js 复制代码
export default function Pull({ children, onLoad, finished, setFinished}) {}

2. 在组件内创建两层div,最外层的div是用来包裹头部下拉后展示部分以及需要实现下拉效果的部分,还一层div就是头部部分,其内放有需要展示的文本内容

js 复制代码
return (
    <div
      className={styles['pull-wrapper']}
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
      //设置容器移动的距离为translateY状态变量
      style={{ transform: `translateY(${translateY}px)` }}
    >
      <div className={styles['pull-header']}>
      //设置下拉文本展示为current状态变量
        <p className={styles['pull-header-title']}>{current}</p>
      </div>

      {children}
    </div>
  )
}

3. 给最外层的div分别绑定onTouchStart,onTouchMove以及onTouchEnd触摸事件

4.定义所需的状态变量

  • 定义状态变量startY用于更新存储手指点击时的y轴坐标

  • 定义状态变量distance用于更新存储手指下拉距离

  • 定义状态变量translateY,用于更新存储容器下拉距离

  • 定义状态变量current用于更新存储文本

js 复制代码
// 初始化触摸起始Y坐标
  const [startY, setStartY] = useState(0)
  // 初始化手指下拉的距离
  const [distance, setDistance] = useState(0)
  // 初始化容器下拉的距离
  const [translateY, setTranslateY] = useState(0)
    // 初始化当前状态文本
  const [current, setCurrent] = useState('下拉刷新...')

5.定义所需的变量

  • 定义max限制手指下拉最大距离

  • 定义middle为所需要的那个阈值

js 复制代码
  // 定义最大下拉距离为250px
  const max = 250
  // 定义触发刷新的中间阈值为100px
  const middle = 100

5. 分别调用onTouchStart,onTouchMove以及onTouchEnd触摸事件

大体思路:调用onTouchStart函数,获取手指开始移动时的y轴坐标位置,并存储在startY中,再调用onTouchMove函数,计算手指下拉距离以及容器下拉距离,并进行最大值判断,手指上滑,以及达到阈值更换文本"释放刷新...",最后调用onTouchEnd,手指松开时更新文本为"加载中...",同时将容器缓慢收缩到最小值,并保持不变,同时向后端发送请求,只有接收到数据后,容器才会继续收缩完全,并将文本改回"下拉刷新...",最后将手指下拉距离重置为0。

js 复制代码
  // 触摸开始事件处理函数
  const onTouchStart = (e) => {
    // 获取触摸起始的Y轴坐标
    const start_y = e.touches[0].clientY
    // 设置触摸起始Y坐标状态
    setStartY(start_y)
  }
  const onTouchMove = (e) => {
  //获取手指移动的实时位置
    const move_y = e.touches[0].clientY;
    //如果手指移动位置在开始移动位置的上方,则不更新容器下拉距离
    if (move_y < startY) return;

    // 实时计算当前手指下拉距离(同步值)
    //计算手指下拉距离
    const currentDistance = move_y - startY;

    // 1. 更新状态(用于后续渲染)
    //更新手指下拉距离
    setDistance(currentDistance);

    // 2. 用同步值做条件判断
    //如果手指下拉距离大于设置的阈值则展示释放刷新...
    if (currentDistance >= middle) {
      setCurrent('释放刷新...');
    }

    // 3. 用同步值做限制检查
    //如果用户手指下拉距离大于最大值则不再更新容器下拉距离
    if (currentDistance > max) return;

    // 4. 用同步值计算位移
    更新容器下拉距离为手指下拉距离的0.8次方,为非线性关系
    setTranslateY(currentDistance ** 0.8);
  }
  
  // 触摸结束事件处理函数
  const onTouchEnd = () => {
    // 如果下拉距离超过中间阈值,松手时,触发刷新
    if (distance >= middle) {
      // 改变状态文本为'加载中...'
      setCurrent('加载中...')
      // 创建定时器,使容器缓慢复位
      timer = setInterval(() => {
        setTranslateY((prev) => prev - 5)
      }, 20)
      // 调用父组件传过来的发送请求的函数
      onLoad()
      // 重置下拉距离
      setDistance(0)
    }
  }

定时器timer声明在Pull组件外

防止每次组件重新渲染timer重新赋值为null,从而使之前的timer丢失,而无法清除。

js 复制代码
let timer = null
// 下拉刷新组件
export default function Pull({ children, onLoad, finished, setFinished }) {}

6. 监听容器收缩位置,到达最小值后则销毁定时器

js 复制代码
  // 监听容器移动距离,当接近初始位置时清除定时器
  useEffect(() => {
    if (translateY <= 40) {
      clearInterval(timer)
    }
  }, [translateY])

7. 监听Finished的值

为父组件穿过来的值,父组件接口请求完成后,则会将Finished改为true,所有子组件则将下拉加载容器收回,并重置相关变量,以及该Finished值为false,为下一关刷新做准备

js 复制代码
  useEffect(() => {
  //当父组件将值改为true时表示刷新完毕
    if (finished) {
      // 重置容器位置
      setTranslateY(0)
      // 重置finished状态
      setFinished(false)
      // 重置状态文本
      setCurrent('下拉刷新...')
    }
  }, [finished])

8. 以下为父组件内部分代码

js 复制代码
  const [finished, setFinished] = useState(false)
  const onLoad = async() => { // 释放重新加载  
   //getData为封装的发送请求的函数
    await getData(1, 10)
    setFinished(true)
  }
  
  //以下就是使用Pull组件包裹的部分
    <Pull onLoad={onLoad} finished={finished} setFinished={setFinished}>
    <section className={styles['section']}>
      <ul>
        {
          noteList.map(item => (
            <li key={item.id} onClick={()=>{navigate(`/noteDetail?id=${item.id}`)}}>
              <div className={styles['img']}>
                {item.note_img ? <img src={item.note_img} alt="" /> : null}
              </div>
              <div className={styles['time']}>{formateDate(item.update_time)}</div>
              <div className={styles['title']}>{item.note_title}</div>
            </li>
          ))
        }
      </ul>
    </section>
    </Pull>
相关推荐
sorryhc21 分钟前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js
uhakadotcom36 分钟前
NPM与NPX的区别是什么?
前端·面试·github
页面仔Dony1 小时前
绝对路径与相对路径的区别及作用
前端·javascript
林太白1 小时前
Zustand状态库(简洁、强大、易用的React状态管理工具)
前端·javascript·react.js
绝无仅有1 小时前
服务器Docker 安装和常用命令总结
后端·面试·github
YuJie2 小时前
vue3 无缝滚动
前端·javascript·vue.js
小野鲜2 小时前
前端打开新的独立标签页面,并且指定标签页的大小,管理新标签页面的打开和关闭(包含源码和使用文档)
前端·javascript
十五_在努力2 小时前
参透 JavaScript —— 解析浅拷贝、深拷贝及手写实现
前端·javascript
王六岁2 小时前
JavaScript值和引用详解:从栈堆内存到面试实战
javascript·面试
Code_Artist3 小时前
[Java并发编程]3.同步锁的原理
java·后端·面试