标准答案

  1. 先知道列表总数、容器高度和单项高度,计算总占位高度。
  2. 监听滚动容器的 scrollTop,计算 startIndex 和 endIndex。
  3. 只渲染 startIndex 到 endIndex 之间的可见项,并适当增加 overscan 缓冲。
  4. 用 transform 或 paddingTop 把可见项移动到正确位置,让滚动位置看起来连续。
  5. 固定高度版本比较简单,动态高度列表还需要高度缓存、测量和滚动位置修正。

题目解析

虚拟列表解决的是 DOM 数量过多导致的渲染和滚动性能问题。数据可以有一万条,但页面上真正需要存在的 DOM 只应该是视口附近几十条。

固定高度版本的关键公式很直接:startIndex 等于 scrollTop 除以 itemHeight 向下取整,visibleCount 等于 containerHeight 除以 itemHeight 向上取整。再加一点 overscan,可以减少快速滚动时的空白感。

动态高度是难点。因为每项高度不同,不能再用简单除法定位,通常要维护每项高度、前缀高度或位置缓存,并在测量后修正滚动偏移。

代码示例

下面是固定高度虚拟列表的核心计算逻辑,真实组件还要接入滚动事件和渲染框架。

JavaScript
function getVirtualRange(options) {
  const {
    scrollTop,
    itemHeight,
    containerHeight,
    total,
    overscan = 3
  } = options

  const visibleCount = Math.ceil(containerHeight / itemHeight)
  const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan)
  const end = Math.min(total, start + visibleCount + overscan * 2)
  const offsetTop = start * itemHeight
  const totalHeight = total * itemHeight

  return {
    start,
    end,
    offsetTop,
    totalHeight
  }
}

常见误区

  • 只截取可见数据,但没有设置总占位高度,导致滚动条长度不对。
  • 没有 overscan,快速滚动时容易看到空白。
  • 把固定高度公式直接套到动态高度列表,滚动位置会越来越不准。

作者信息