下拉刷新需在 scrolltop === 0 且手指下移时才干预,仅此时调用 preventdefault(),结合 translatey 动画与防抖机制,确保与原生滚动共存。

如何用 touchstart/touchmove/touchend 检测下拉刷新手势
移动端下拉刷新本质是监听用户手指在 document 或容器上的垂直拖动距离,当向下位移超过阈值且松手时触发刷新。关键不是“实现一个刷新动画”,而是准确捕获并区分「下拉意图」和「普通滚动」。
常见错误是直接给 body 绑定 touchmove 并阻止默认行为——这会禁用页面原生滚动,导致内容无法滑动。正确做法是只在用户确实处于顶部且向下拖动时才干预。
- 记录
touchstart时的scrollTop和touches[0].clientY - 在
touchmove中判断:仅当scrollTop === 0且deltaY > 0(手指下移)时,才允许记录下拉偏移量 - 必须在
touchmove中调用event.preventDefault(),但仅限该条件成立时;否则保留原生滚动 - 避免在非目标容器上监听,否则可能误触(比如轮播图内部也响应了 touch 事件)
为什么 scrollHeight、scrollTop 和 clientHeight 的差值不等于可滚动高度
计算是否到达顶部不能只看 scrollTop === 0,还要确认当前容器是否真的可向上滚动。很多开发者用 scrollHeight - scrollTop 判断“到底部”,但顶部判断容易忽略一个事实:<code>scrollTop 最小值确实是 0,但某些布局(如 flex 容器 + overflow: auto)或 iOS Safari 下,scrollTop 可能因弹性滚动而短暂为负值。
- 安全判断顶部应为
element.scrollTop ,而非严格等于 <code>0 - 获取容器需明确指定,不要依赖
document.scrollingElement(iOS 不稳定),优先用目标刷新容器(如main或#feed) -
element.scrollHeight - element.clientHeight是理论最大滚动高度,但实际scrollTop不会超过它;而scrollTop小于 0 说明发生了 overscroll,此时正是下拉刷新的窗口期
防止 touchcancel 干扰与 iOS Safari 的特殊处理
iOS Safari 在快速下拉时容易触发 touchcancel,尤其在状态栏区域附近抬起手指,导致 touchend 不执行,下拉状态丢失。这不是 bug,而是系统级手势拦截(如返回上一页)。
- 必须监听
touchcancel,并视同touchend处理:清空缓存的起始位置、重置下拉状态 - iOS 15+ 对
overscroll-behavior: contain支持良好,可在刷新容器上设置,抑制全局弹性滚动对判断的干扰 - 不要依赖
gesturestart或pinch事件——它们和下拉刷新无关,且兼容性差 - 调试时可用
console.log(event.cancelable, event.type)确认哪些操作触发了 cancel
refreshThreshold 和松手后逻辑怎么写才不卡顿
阈值(如 60px)不是越大越好。设太高用户感知迟钝,太低又容易误触发。更重要的是松手后的反馈节奏:不能等 touchend 后立刻发请求,得先做视觉反馈(如旋转图标),再延迟触发。
- 推荐阈值:40–70px,具体按产品动效节奏调整;用
Math.abs(deltaY)计算,避免方向混淆 - 松手时若
pullDistance >= refreshThreshold,执行「回弹动画」+「加载态切换」,再用setTimeout(() => { loadNewData() }, 300)延迟请求(模拟自然等待感) - 动画必须用
transform: translateY()实现,不要改top或margin,否则触发重排 - 整个过程要加防抖:同一周期内重复下拉不重复注册事件,
touchstart前检查是否有 pending 状态
真正难的不是监听手势,而是让下拉过程和原生滚动共存、不打架,同时在各种 iOS 版本和 Chrome Android 上保持行为一致。大部分线上问题都出在没处理好 touchcancel 或盲目阻止默认行为。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











