PullToRefresh 在移动端列表里为啥不生效?
我用的是原生 JS 实现的下拉刷新,监听 touch 事件做的,但在 iOS Safari 上完全没反应,安卓部分机型也偶尔失灵。
试过加 touch-action: none,也阻止了默认滚动,但还是不行。这是我的核心逻辑:
let startY = 0;
const refreshEl = document.querySelector('.pull-refresh');
document.addEventListener('touchstart', e => {
startY = e.touches[0].pageY;
});
document.addEventListener('touchmove', e => {
const moveY = e.touches[0].pageY;
const offset = moveY - startY;
if (offset > 0 && window.scrollY === 0) {
refreshEl.style.transform = <code>translateY(${offset * 0.3}px)</code>;
}
});
是不是漏了什么关键点?比如需要配合 overscroll-behavior 或者别的 meta 标签?
touchmove的默认行为处理特别严格,尤其是当页面可滚动时,即使你判断了window.scrollY === 0,它也可能不触发touchmove或中途被拦截。你目前的写法有几个硬伤:
1. 监听的是
document上的touchstart和touchmove,但移动端很多情况下滚动是发生在body或某个具体容器上(比如你列表外面包了一层overflow: auto的 div),这时候window.scrollY可能根本不会变,或者滚动起点根本不在window上;2. 没有显式调用
preventDefault(),iOS Safari 在滚动边界(比如顶部下拉)时会默认触发“橡皮筋”效果,你的touchmove可能被系统抢走了;3.
touchmove里没有做passive: false的声明,导致即使你写了preventDefault()也可能无效(Chrome 和 Safari 默认把touch事件设为 passive);正确做法是:
- 把监听器绑定在实际滚动的容器上(比如你的列表容器),而不是
document;- 初始化监听时加上
{ passive: false };- 在
touchmove里,当检测到下拉且处于顶部时,主动preventDefault()阻止默认滚动;- 滚动容器的
touch-action要设成none(但注意是绑定事件的那个元素,不是body);给你一个改好的简化版参考:
另外记得给滚动容器加样式:
如果还是不行,大概率是滚动容器没选对,你得确认一下到底是谁在滚动——有时候你以为是
window滚,其实被某个fixed布局或者position: absolute的元素遮住了,实际滚动的是内部 div。window.scrollY === 0。关键点有两个:
第一,iOS Safari 下,如果滚动的是
body,那window.scrollY并不可靠,尤其当页面内容少于一屏时,它压根不会触发滚动,你监听的touchmove会被浏览器自己拦截做“橡皮筋回弹”动画。第二,你没处理
touchmove的preventDefault(),浏览器默认会滚动页面,但 iOS 上如果滚动区域不是body(比如你用了固定高度的overflow: auto容器),那window.scrollY根本不会变,你永远进不了条件判断。解决方案直接上代码:
另外别忘了在 meta 里加上这个(虽然不是必须,但能减少 iOS 的手势冲突):
最后提醒一句:别用
document.addEventListener('touchmove', ...)全局监听,如果页面里有其他可滚动容器(比如弹窗里的列表),会把滚动也吃掉——实际项目里建议只在目标容器上加监听,或者加个 flag 控制。