跳到主要内容
浏览器
导航
文件浏览器

大目录虚拟滚动实现

虚拟滚动用有限数量的渲染节点表示任意长度的目录列表。本文讨论定高场景下滚动偏移与索引的换算、缓冲区与节点回收、动态高度的测量与估算、索引偏移表,以及增量加载与滚动锚定的配合。

发布于 4 分钟阅读 1494 字

一个包含数万条目的目录若整批渲染为节点,会带来可观的布局与内存开销。虚拟滚动的思路是只在视口附近保留渲染节点,其余行用占位高度表示。列表结构本身与文件系统的关系,可参见 文件系统抽象层与虚拟列表。

定高虚拟滚动的基本模型

行高固定时,滚动偏移与可见行区间之间是线性关系。设行高为 rowHeight、容器滚动偏移为 scrollTop、视口高度为 viewportHeight,起始与结束索引可以直接由除法得到:

const start = Math.floor(scrollTop / rowHeight);
const visibleCount = Math.ceil(viewportHeight / rowHeight);
const end = Math.min(start + visibleCount, totalCount);

只创建 [start, end) 区间内的节点,其余行不落地为 DOM。为了让滚动条长度与内容规模一致,通常用一个占位元素把高度设为「行数乘以行高」。

缓冲区与节点回收

只渲染可见区间时,快速滚动容易出现短暂空白:新行尚未创建,旧行已被移除。多数实现会在可见区上下各扩展若干行作为缓冲区,使渲染区间略大于可见区间:

区域渲染策略作用
索引小于 start 减缓冲区不渲染,回收节点控制节点总数
start 减缓冲区 至 end 加缓冲区保持渲染吸收滚动过程中的短暂空白
索引大于 end 加缓冲区不渲染,回收节点控制节点总数

回收节点时可以复用元素对象,把新的行数据写入已有元素,减少频繁创建与销毁带来的开销。缓冲区行数越大,空白越不容易出现,同时常驻节点数也越多,取值需要在平滑度与节点数量之间权衡。

动态高度:测量与估算

条目名称较长而折行、或列表带有缩略图时,行高不再一致。此时高度的来源有两种思路:

  • 测量:先渲染一次并读取真实高度,再写回索引表
  • 估算:按内容特征给出初始高度,待真实值到达后校正

测量会引入布局读取,因此需要把测量集中在同一批次内完成,减少反复触发布局。估算的好处是不必先行渲染即可给出总高度,代价是校正已滚过的行时需要补偿滚动偏移,否则视口内容会跳动。多数实现把两者结合:对已进入过视口的行使用实测高度,未进入过的行保留估算值。

索引偏移表

高度不一致时,某行的顶部位置等于它之前所有行高之和。若每次查询都做一次求和,复杂度与索引规模成正比。常见做法是维护前缀和数组,使查询与更新均可通过二分查找定位:

function findRow(offsets, y) {
  let lo = 0, hi = offsets.length - 1;
  while (lo < hi) {
    const mid = (lo + hi + 1) >> 1;
    if (offsets[mid] <= y) lo = mid;
    else hi = mid - 1;
  }
  return lo;
}

单行高度变化后,其后所有行的偏移都需要调整,逐项更新与索引规模成正比。因此多数实现只在少量行发生变化时才做全量重建,或改用平衡树、跳表等支持区间增量的结构维护偏移。

稀疏数据的增量加载

目录条目数量可能超过单次列举返回的规模,数据源因此需要支持分页或游标。滚动方向决定了取数顺序:向下滚动时追加尾部数据,向上滚动时可能需要在头部插入。头部插入会让已有索引整体后移,因此需要一份稳定的标识与位置映射,避免行的身份随插入而变化。

常见策略是为总数给出一个上界,当滚动接近已加载区间的边界时提前发起下一批请求,并把返回顺序与排序键一并保存,使结果可复现。尚未取回的行可以用占位行渲染,待数据到达后原位替换。

滚动锚定

内容高度在滚动过程中变化时,浏览器可能调整 scrollTop 以保持锚点,这一行为通常称为滚动锚定。虚拟列表自带的高度补偿与浏览器的锚定机制可能叠加,因此在动态高度场景中,多数实现会显式关闭浏览器的自动锚定,改为自行维护「锚点行索引 + 行内偏移」两个量,在数据变更后按这两个量还原视口位置。

键盘导航与焦点管理

键盘导航要求当前焦点元素位于视口内,这会引发滚动。虚拟列表需要区分两类滚动:由指针或滚轮输入引起的滚动,以及由焦点移动引起的滚动。若不加区分,焦点移动触发滚动、滚动又触发焦点更新,可能形成相互触发的循环。

常见处理方式是把焦点位置保存在数据层(一行索引),DOM 元素只作为渲染结果。当目标行不在渲染区间时,先滚动到该行所在的偏移位置,待渲染完成后再把焦点交给对应元素。这样焦点状态不依赖某个具体节点是否存活,节点被回收也不影响键盘导航的连续性。

小结

虚拟滚动的核心是用有限数量的渲染节点表示任意长度的列表。定高场景下偏移与索引可以直接换算;高度不一致时,需要高度来源、前缀和索引表与滚动锚定三者配合。增量加载则要求数据源给出稳定的顺序与总数上界,索引才能在追加与回填后保持有效。

文件浏览器 · 相关文章