前言
下拉刷新的原理: 实际上就是创建一个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>