下拉刷新(Pull-to-Refresh)与上拉加载(Infinite Scroll)的定义
下拉刷新:用户手指向下滑动屏幕时触发数据刷新的交互设计,主要用于重新加载最新内容(如新消息、动态更新)。
上拉加载:用户滑动到页面底部时自动加载更多数据的交互模式,用于分批次加载历史内容(如商品列表、新闻分页)。
核心作用对比
特性 | 下拉刷新 | 上拉加载 |
---|---|---|
触发方向 | 屏幕顶部向下滑动触发 | 屏幕底部向上滑动触发 |
数据流向 | 加载最新数据(时间倒序) | 加载更多历史数据(时间正序) |
用户需求 | 即时获取最新信息(如微博、聊天列表) | 浏览更多内容(如电商商品、长列表) |
实现方案
JavaScript Touch
一、Touch 事件基本类型与触发机制
四大核心事件
touchstart
: 手指首次接触屏幕时触发,用于捕获触摸起点
touchmove
: 手指在屏幕上滑动时持续触发,常用于实现拖动或滑动效果
touchend
: 手指离开屏幕时触发,标记触摸结束
touchcancel
: 系统中断触摸时触发(如来电、弹窗打断),需处理异常中断场景
事件触发顺序
典型流程为 touchstart → touchmove → touchend
。若中途发生中断,则触发 touchcancel
替代 touchend
。
二、TouchEvent 对象关键属性
通过事件回调参数可获取 TouchEvent
对象,其包含以下关键数据:
-
touches
: 当前屏幕上所有触点的列表(包含多指触控信息) -
targetTouches
: 当前元素上活动的触点集合 -
changedTouches
: 触发当前事件的触点集合(如touchend
事件中记录刚移除的触点)
三、与鼠标事件的对比与兼容处理
-
行为差异
- 移动端优先响应
touch
事件,随后可能触发mouse
事件(如click
)5 click
事件在移动端有 200-300ms 延迟(历史设计为区分单击与双击缩放)5
- 移动端优先响应
-
替代方案
可通过自定义 tap
事件 消除延迟,示例实现逻辑:
function bindTap(element, callback) {
let startTime = 0, isMove = false;
element.addEventListener('touchstart', () => startTime = Date.now());
element.addEventListener('touchmove', () => isMove = true);
element.addEventListener('touchend', (e) => {
if (!isMove && Date.now() - startTime < 200) callback(e);
});
}
此方法通过判断触点移动状态和时间差模拟即时点击
技术实现关键点
-
下拉刷新
- 触发条件:滑动距离超过阈值(如 60px)且释放手指
- 加载状态:显示加载动画(如转圈图标)和提示文案(“正在刷新...”)
- 数据更新:向服务器请求最新数据,清空或覆盖旧数据
<div class="refresh-container">
<div class="refresh-indicator">↓ 下拉刷新</div>
<div class="content">页面内容</div>
</div>
// 存储触摸起始点的 Y 坐标
let startY = 0;
// 标识是否正在刷新中(防止重复触发)
let isRefreshing = false;
const container = document.querySelector('.refresh-container');
// 提示文本元素
const indicator = document.querySelector('.refresh-indicator');
container.addEventListener('touchstart', e => {
// 如果正在刷新 或 页面不在顶部,直接返回(避免误触)
if (isRefreshing || document.documentElement.scrollTop > 0) return;
// 记录初始触摸点的 Y 坐标(用于计算滑动距离)
startY = e.touches.pageY;
});
container.addEventListener('touchmove', e => {
// 同上:防止刷新中或页面滚动时触发
if (isRefreshing || document.documentElement.scrollTop > 0) return;
// 计算滑动距离(当前 Y 坐标 - 起始 Y 坐标)
const moveY = e.touches.pageY - startY;
// 仅处理下拉动作(moveY > 0 表示向下滑动)
if (moveY > 0) {
/ 通过 CSS transform 移动容器位置(实现视觉下拉效果)
container.style.transform = `translateY(${moveY}px)`;
// 根据滑动距离切换提示文本
indicator.textContent = moveY > 50 ? "↑ 释放刷新" : "↓ 下拉刷新";
}
});
container.addEventListener('touchend', async e => {
// 计算最终滑动距离(使用 changedTouches 确保触摸点已结束)
const moveY = e.changedTouches.pageY - startY;
// 满足条件:滑动超过 50px 且未在刷新中
if (moveY > 50 && !isRefreshing) {
// 锁定刷新状态
isRefreshing = true;
indicator.textContent = "加载中...";
await fetchData(); // 数据请求
container.style.transform = "translateY(0)";// 复位容器位置
indicator.textContent = "↓ 下拉刷新";
isRefreshing = false; // 释放刷新锁
}
});
扩展优化方向
-
滑动阻力效果
// 添加阻力系数(如滑动距离超过 100px 时减速)
const dampenedY = moveY > 100 ? 100 + (moveY - 100) * 0.5 : moveY;
container.style.transform = `translateY(${dampenedY}px)`;
2.加载成功/失败提示
try {
await fetchData();
indicator.textContent = "加载成功!";
} catch (error) {
indicator.textContent = "加载失败,请重试";
}
3. 动画复位效果
container.style.transition = "transform 0.3s ease"; // 添加复位动画
container.style.transform = "translateY(0)";
2.上拉加载
1. 滚动监听方案
//标记当前是否正在加载数据,防止重复请求
let isLoading = false;
//监听页面滚动事件,使用 throttle 节流函数,控制触发频率为 200ms(固定时间间隔内只执行一次(这里是每200ms))
window.addEventListener('scroll', throttle(() => {
//scrollTop:滚动条垂直偏移量(已滚动距离)
//clientHeight:可视区域高度(不包含滚动条)
//scrollHeight:整个文档的总高度(包含不可见部分)
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
const threshold = 100; // 距离底部100px触发
if (scrollTop + clientHeight >= scrollHeight - threshold && !isLoading) {
isLoading = true;
loadMoreData().finally(() => isLoading = false);
}
}, 200));
// 节流函数
function throttle(fn, delay) {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
2. IntersectionObserver API(现代浏览器支持)
Intersection Observer API 是一种用于异步监测目标元素与视口(或指定父容器)交叉状态的浏览器原生 API,可高效实现以下功能:
核心特性
-
异步监听
- 基于浏览器渲染线程独立工作,避免主线程阻塞
- 替代传统
scroll
事件 +getBoundingClientRect
计算
-
高性能
- 自动优化多元素监测,无需手动计算位置
- 适用于无限滚动、图片懒加载等高频场景
-
灵活配置
- 支持设置触发阈值 (
threshold
)、根容器 (root
)、边距 (rootMargin
) 等参数
- 支持设置触发阈值 (
<div class="loader">加载中...</div>
const observer = new IntersectionObserver(entries => {
if (entries.isIntersecting && !isLoading) {
isLoading = true;
loadMoreData().finally(() => isLoading = false);
}
});
observer.observe(document.querySelector('.loader'));
核心注意事项
问题类型 | 解决方案 |
---|---|
iOS橡皮筋效果 | 添加CSS代码:body { overscroll-behavior: contain; } |
安卓兼容性 | 在touchmove事件中调用 e.preventDefault() 阻止默认滚动行为 |
频繁触发请求 | 使用节流(throttle)/防抖(debounce) + 请求锁(loading状态) |
内容高度不足 | 当页面高度未占满屏幕时,自动触发首次加载 |
快速滑动白屏 | 添加加载骨架屏(Skeleton Screen)优化体验 |