移动端下拉刷新的实现

前言

下拉刷新的原理: 实际上就是创建一个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>
相关推荐
崎岖Qiu42 分钟前
【JVM篇11】:分代回收与GC回收范围的分类详解
java·jvm·后端·面试
再学一点就睡3 小时前
手写 Promise 静态方法:从原理到实现
前端·javascript·面试
再学一点就睡4 小时前
前端必会:Promise 全解析,从原理到实战
前端·javascript·面试
OEC小胖胖6 小时前
性能优化(一):时间分片(Time Slicing):让你的应用在高负载下“永不卡顿”的秘密
前端·javascript·性能优化·web
小小李程序员6 小时前
JSON.parse解析大整数踩坑
开发语言·javascript·json
宋辰月6 小时前
Vue2-VueRouter
开发语言·前端·javascript
haaaaaaarry7 小时前
Element Plus常见基础组件(一)
java·前端·javascript·vue.js
Goboy7 小时前
分库分表后ID乱成一锅粥
后端·面试·架构
拾光拾趣录7 小时前
前端灵魂拷问:从URL到Redux,17个常见问题
前端·面试
萌萌哒草头将军8 小时前
Prisma ORM 又双叒叕发布新版本了!🚀🚀🚀
前端·javascript·node.js