无限滚动(Infinite Scroll)是现代内容类产品的标配交互——微博、抖音、朋友圈无一例外。区别于传统的分页(Pagination),无限滚动让用户无需主动翻页,数据会随着滚动悄无声息地追加进来。
但在实现上,无限滚动藏着很多"反直觉"的工程坑:
scroll 事件 + 计算 scrollTop 来判断是否触底?——高频触发 + 强制同步布局,性能灾难。本文将系统地探讨正确实现无限滚动的两种主流方案及其核心原理。
最直觉的做法:监听容器的 scroll 事件,判断 scrollTop + clientHeight >= scrollHeight,满足条件时请求下一页数据。
缺陷:
scrollTop、clientHeight、scrollHeight 等布局属性,每次都有强制回流的风险。IntersectionObserver ⭐这是目前业界最推荐的方案。
你不需要自己去算 scrollTop,只需要在列表末尾放一个"哨兵元素(Sentinel)",然后用 IntersectionObserver 去监听这个哨兵是否进入了视口。一旦哨兵进入视口,就说明用户滚到了底部,此时加载下一页数据并追加进列表。
浏览器底层使用的是异步的 Intersection 检测,完全不会触发强制回流,性能极高。
完整 React 实现 Demo:
当数据量极大(比如已加载了 10000 条记录),如果仍然把所有 DOM 节点全留在页面上,内存消耗和渲染压力会让页面非常缓慢,这时就需要引入**虚拟列表(Virtual List)**技术:
这在工程上通常直接使用现成的库,如 react-window、react-virtual、Vue 的 vue-virtual-scroller。
Q:如何实现一个无限滚动功能?当列表数据量非常大时如何优化?
回答思路:
scroll 事件监听(需要结合节流)虽然可行,但因为需要读取布局属性有性能风险。推荐使用 IntersectionObserver 监听列表末尾的"哨兵元素",该 API 是浏览器原生的异步检测机制,完全不触发强制回流,且节省了人工节流的成本。IntersectionObserver 监听哨兵进入视口事件 → 触发数据加载 → 新数据追加到列表末尾 → 哨兵元素被重新推到新的底部。react-window 或 react-virtual 等成熟库。