外观
怎么做无限滚动的长列表?
窗口里只渲染看得见的项,总高度用占位撑开,这是虚拟列表。滚到底再要下一页时,用哨兵触发请求,并用一把锁或请求序号丢掉重复和过期的结果。
原理
高度固定时,滚动位置可以直接换算下标:
js
const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan)
const visibleCount = Math.ceil(viewportHeight / itemHeight) + overscan * 2
const end = Math.min(total, start + visibleCount)
const offset = start * itemHeight容器高度是 total * itemHeight。真正的 DOM 只有 start 到 end 这一段,再用 transform: translateY(offset) 或上方的 padding 把它推到正确位置。overscan 是上下多渲染的几行,用来盖住快速滚动时的空白。
高度不固定时,渲染后测量每一项,把高度记下来,并维护前缀和。下次用二分在前缀和里找 scrollTop 对应的起点。没量过的项先用估计高度,量完再纠正滚动位置,否则滚动条会跳。
继续加载用列表底部的哨兵,交给 IntersectionObserver,不要在滚动事件里自己算「剩下不到 200px」。请求中的标志或 AbortController 保证不会并发打出好几页。没有更多数据时卸掉哨兵。
一万条数据一次性变成一万个节点,时间会花在创建 DOM、样式和布局上。虚拟列表把节点数限制在视口附近。如果一页本来就只有几十条,普通分页更简单,不必上虚拟列表。
追问
搜索、筛选之后怎么处理?
筛选结果是另一份列表:重置 scrollTop、清空高度缓存、从第一页重新请求。旧请求的响应回来时直接丢弃。见 搜索与请求竞态。
键盘和无障碍怎么办?
只挂载可见 DOM 时,浏览器自己的查找和部分屏幕阅读器只能看到这一截。面试里要主动说这个取舍。数据量没大到卡顿时,优先渲染完整列表。
易错点
- 用数组下标当
key,滚动复用节点时输入状态会串。用数据 id。 - 行高算错一点点,快速滚动就会出现空白或重叠。
- 哨兵一直可见(容器没有固定高度)时,会连续把所有页拉完。