用 intersectionobserver 替代 scroll 事件监听可提升性能,因其不阻塞主线程、不触发重排,但需注意 rootmargin 设置、isintersecting 双重校验、容器高度约束及移动端兼容性;无限滚动需严格管理加载状态、终止条件与 dom 插入逻辑。

用 IntersectionObserver 替代 scroll 事件监听
直接绑定 window.addEventListener('scroll') 是性能陷阱:它每帧都可能触发,尤其在低端设备或复杂页面上,容易卡顿甚至丢帧。IntersectionObserver 不走主线程,不触发重排,且语义清晰——你不是在“监听滚动”,而是在“观察某个元素是否进入视口”。
实操要点:
- 在列表末尾插入一个占位节点:
<div id="sentinel"></div> - 创建 observer 时设
rootMargin: '200px'(提前 200px 触发),避免用户拉到底才动 - 必须用
entries[0].isIntersecting === true做双重校验,防止回调被缓存或误触发 - 移动端需确保容器有明确高度和
overflow-y: auto,否则 observer 可能失效(body 滚动时 root 默认是 document,但很多 UI 框架会改用局部容器)
加载状态与终止条件必须显式管理
无限滚动最常崩在“以为后端返回空数组就该停”,结果网络错误、游标失效、分页越界都返回空,前端却继续死循环请求。hasMore 不是可选字段,是协议契约。
关键判断要分层:
-
fetch抛异常 → 重试最多 1 次,之后设loading = false并显示“点击重试”按钮 - 响应成功但
response.data.length === 0或response.hasMore === false→ 立即observer.unobserve(sentinel),移除监听 - 游标字段(如
next_cursor)为null或空字符串 → 同上,视为终点 - 任何一次加载失败后,
loading必须恢复为false,否则后续滚动永远被锁死
DOM 插入必须批量且锚定滚动位置
用 innerHTML += htmlString 看似省事,但每次都会触发完整 HTML 解析 + DOM 重建 + 重排,列表超过 200 条时性能断崖下跌;更糟的是,iOS Safari 在键盘弹起后恢复滚动时,会因 DOM 重置导致跳屏。
正确做法:
- 用
document.createDocumentFragment()批量构建新节点,再一次性appendChild到容器 - 插入前记录
container.scrollTop,插入后立即container.scrollTop = scrollTop,视觉无跳变 - 若列表项含交互(如点赞按钮),绑定事件委托到容器,别给每个新节点单独绑
onclick - 避免在插入前清空整个容器(如
container.innerHTML = ''),否则已挂载的图表、高亮代码、焦点状态全丢失
移动端 touchmove 干扰与兼容性兜底
iOS 和部分安卓 WebView 中,惯性滚动会让 IntersectionObserver 多次回调;某些老机型(如 iOS 11.4)不支持 rootMargin,或 isIntersecting 返回不稳定值。
防御性写法:
- 滚动容器加 CSS:
touch-action: pan-y,禁用横向拖拽干扰 - observer 回调里加简单防抖:
setTimeout延迟 100ms 再执行加载,clearTimeout上次未执行的定时器 - 降级方案:检测
'IntersectionObserver' in window,不支持时 fallback 到节流后的scroll计算,公式用container.scrollHeight - container.scrollTop - container.clientHeight (注意必须用容器自身属性,不是 <code>window) - 所有动态插入的 HTML 片段,必须过一遍
DOMPurify.sanitize()或至少过滤<script></script>和onerror=类属性,防 XSS
真正难的不是“怎么让它动起来”,而是“怎么让它在弱网、切后台、键盘弹起、缩放页面、连续快速滚动这些真实场景下都不崩”。每一个看似边缘的 case,上线后都会变成监控报警里的高频错误。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











