虚拟滚动

虚拟滚动在前端是一个很常见的性能优化解决方案。当我们需要渲染大量的dom的时候(比如几十万个列表数据),由于浏览器的内存限制,几十万个dom节点全部渲染出来就会导致页面滚动卡顿。而虚拟滚动就是模拟浏览器的原生滚动方式,按需加载列表元素,避免大量渲染dom元素造成卡顿。

基本思路

  • 根据容器高度和每个列表项的高度计算出我们需要展示多少条数据
  • 发生滚动的时候记录滚动高度
  • 根据滚动的高度截取应该展示数组中的那些数据

对于每个列表项的高度,我们这里可以分为定高的列表项和不定高的列表项

image-20230716172355298

定高

定高的思路比较简单,首先初始化的时候,记录容器的高度,列表项的高度,从而计算出,当前视口要展示的元素的起始索引,

/**
* list 带有高度的数组
* offset 滚动offset
*/
const findIndexOverHeight = (list: ICalculationList[], offset: number) => {
      let currentHeight = 0;
      for (let i = 0; i < list.length; i++) {
        currentHeight += list[i].domHeight;
        if (currentHeight > offset) {
          return i;
        }
      }
      return list.length - 1;
  }

我们的开始索引就是offset=0 所计算出来的位置,结束索引就是 offset+containerHeight 所计算出来的高度

let offset = 0;
let start = findIndexOverHeight(list, offset);
let end = findIndexOverHeight(list, offset+containerHeight);

所以我们根据上述的函数就可以计算出来视图展示所要的数据范围了,再使用slice(startIndex,endIndex)就可以截取需要展示的数据,然后我们再将这些数据渲染到视图中。

但是上述渲染出来后可以发现,我们都是从头到位完整的渲染这些数据项,而滚动有时候并不会都将视图中的数据项都完整的展示出来,有些数据项可能只展示了一部分,这里就需要设置偏移量来使我们的滚动贴合原生滚动的效果。可以看到图中的renderOffset就是偏移量,计算方式就是 滚动偏移量 减去 视图起始位置元素距离顶部的高度 ,所以为了设置偏移量,还需要设置一个包裹元素来包裹所渲染的列表项,我们的偏移量就加在这个包裹元素上,我们一般可以使用translateY来设置。

不定高

相比定高,不定高就在渲染前设置一个预估高度,这个高度指的是每个数据项的最小高度,然后再渲染出来列表元素后获取每一个元素的实际高度,最后按照定高的逻辑进行重新渲染。

总结

对于虚拟列表,主要思路就这些,我参照其他优秀博主的方案实现了一个React版的虚拟滚动,主要功能和问题如下:

  • 自定义的滚动条
  • 支持无限加载,滚动到底部再添加新数据
  • 10万+的数据,定高和非定高都可以流畅滚动 百万级数据快速拖滚动条会卡顿,定高的不会,主要是非定高的需要重新计算高度,所以时间都消耗在计算上了。
  • 数据量超过10万的情况快速将滚动条从顶部拖动到底部可能会出现滚动不到最后一项的bug,这点还没解决。
  • 对于列表项中有图片的情况,这个场景比较少见,所以暂时没有实现,实现思路的话也是和非定高一样,只是需要动态监听每一个列表项的高度变化,然后再进行更新。

源码

代码示意

参考

本文只是简略说了下思路,详细的可以看如下文章

新手也能看懂的虚拟滚动实现方法

Last modification:July 16, 2023
如果觉得我的文章对你有用,请随意赞赏