Skip to content

虚拟列表,只渲染看得见的部分

长列表的性能问题,核心就一句话:DOM 元素太多了。一万条数据生成一万个 DOM 节点,浏览器光是维护这个 DOM 树就要消耗大量内存,滚动起来卡顿是必然的。

虚拟列表的思路很简单:不管数据有多少条,我只渲染用户当前能看到的那几项。

核心原理

虚拟列表基于一个事实:用户在一个固定高度的容器里滚动,任何时候能看到的条目数量是有限的。比如容器高 600px,每项高 100px,那用户最多只能看到 6 项。

所以不管数据是 100 条还是 10000 条,DOM 里始终只保留 6-8 个节点(加几个缓冲区防止闪烁)。

实现要点

1. 计算可见范围

javascript
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + visibleCount;
const renderList = data.slice(startIndex, endIndex);

2. 视觉欺骗

为了让滚动条显示正确的总长度,需要把列表容器的高度设置为 data.length * itemHeight。然后用 transform: translate3d(0, ${startIndex * itemHeight}px, 0) 把可见区域移到正确位置。

这样用户滚动时,滚动条的位置是对的,但实际渲染的只有那几项。

3. 滚动节流

滚动事件触发非常频繁,需要用 requestAnimationFrame 节流:

javascript
function rafThrottle(fn) {
  let lock = false;
  return function (...args) {
    requestAnimationFrame(() => {
      if (lock) return;
      lock = true;
      fn.apply(this, args);
      lock = false;
    });
  };
}

适用场景

虚拟列表适合固定高度的列表项。如果每项高度不固定,就需要额外的高度缓存和动态计算,复杂度会高很多。

我最早接触虚拟列表是在做停车业务的订单列表时,几千条订单数据直接渲染,页面卡得不行。改成虚拟列表后,DOM 节点从几千个降到了十几个,滚动流畅了很多。

有时候性能优化就是这样——不是做了什么复杂的事,而是想明白了"不需要做什么"。