React虚拟滚动介绍与实现

📖 阅读分享

维持死刑制度的既不是国民也不是国家,而是杀人犯自己! — 《消失的13级台阶》 [日] 高野和明 

虚拟滚动: 复用你的DOM元素,根据用户的滚动方向移除你视口的一个元素。

当列表需要展示上千上万级别甚至是趋向于无限的数据时,DOM元素的堆积会导致浏览器渲染的性能降低,导致用户体验降低甚至出现页面假死状态。这个时候使用虚拟滚动的技术,可以让DOM数量维持在固定的数量,从而解决以上问题。但对于少量数据的场景下,使用传统的加载方式会优于虚拟滚动,虚拟滚动的核心是监听滚动事件去进行复杂的逻辑计算,两者衡量之下,少量的数据的DOM消耗要远小于虚拟滚动的计算

已有虚拟滚动技术的应用: Google Music, Twitter, Facebook。

文章接下来会讲述 固定高度的虚拟滚动 到 动态高度的虚拟滚动是如何实现,技术的难点是什么,并以React的框架为基础,用代码的形式去介绍算法核心点。

源码可以参考此处

固定高度

为了实现跟传统列表加载的体验一样,虚拟滚动需要做到以下几点:

  1. 计算容器能承载的DOM元素容量
  2. 模拟滚动高度
  3. 实时计算显示的元素

1. 承载容量

在计算机领域中有一个名词叫视口,代表当前可见的计算机图形区域。 在虚拟滚动技术中,视口是承载DOM元素的容器,超出该容器的DOM元素是不可见的,需要通过滚动来展示,如下图,白色部分的高度就是容器的高度,蓝色的DOM元素就是用户可见的元素。

假设容器高度为H, 单个DOM元素的高度为DH,那么容器可见的数量为 VISIBLE_COUNT = H / DH。但在实际滚动过程中,仅仅渲染可见的数量是不够的,因为滚动过程的实时计算会导致浏览器的渲染不够及时,可能会出现留白的情况,所以我们需要在上下两边都加上一个缓存用的DOM元素。 假设BUFFER_SIZE是3, 按照上图所示,则真正需要渲染的DOM的数量是 4 + 3 * 2 = 10个, ITEM_COUNT = ⌈H / DH⌉ + BUFFER_SIZE * 2;

为了更方便去过滤该渲染的DOM元素,那么我们设置两个变量去筛选,firstItem和lastItem。

useLayoutEffect(() => {
  ELEMENT_HEIGHT = outerHeight(itemRef.current);
  const containerHeight = containerRef.current?.clientHeight ?? 0;
  VISIBLE_COUNT = Math.ceil(containerHeight / ELEMENT_HEIGHT);
  setLastItem(VISIBLE_COUNT + BUFFER_SIZE);
}, [])

2. 模拟滚动高度 

因为要让用户感受跟传统滚动一样的效果和体验,然而有限的DOM元素不足以撑开上千个数据的列表,所以我们要用css去帮忙撑开,撑开的方式有两种:

  1. 通过设置容器的padding-bottom,让其出现滚动条
  2. 设置一个哨兵元素,并设置哨兵元素的translateY的值,同样能使容器出现滚动条。

💡PS:哨兵在现实中,是用来解决国家之间的边界问题,不直接参与生产活动。在计算机领域也是为了处理边界的问题,可以减少很多边界问题的判断,降低代码复杂性。

我这边推荐第二种方式,理由是:浏览器的重排与重绘。特别是后面介绍动态高度滚动的时候,会不断计算可滚动的高度,这对于性能来说也是一些优化,既然能优化,当然要优化得彻底一些。

因为每个DOM元素的高度是固定的,所以只要每次列表有变化的时候,再对这个高度进行计算就可以,scrollHeight = list.length * ELEMENT_HEIGHT;

<div onScroll={scroll} ref={containerRef} className={styles.container}>
	// 哨兵,用于撑开滚动高度
  <div className={styles.sentry} style={
  { transform: `translateY(${scrollHeight}px)` }} ></div>
	// 可以先忽略下方的代码
  {
    visibleList.map((item, idx) =>
      <div key={idx} style={
  {transform: `translateY(${item.scrollY}px)`}} className={styles.wrapItem} >
        <Item ref={itemRef} item={item} />
      </div>
    )
  }
</div>
useLayoutEffect(() => {
	// 可先忽略这段代码
  list.forEach((item, idx) => {
    item.scrollY = idx * ELEMENT_HEIGHT;
  })
	// list变化时,更新scrollHeight的值
  setScrollHeight(list.length * ELEMENT_HEIGHT);
}, [list]);

3. 实时计算显示的元素

如何正确显示当前已滚动高度所对应的元素呢?这是本节所要解决的问题,也是整个虚拟滚动技术(固定或动态)的核心。

在固定高度的情况下,其实在加载list的时候,已经确定每个元素的位置,只要像哨兵一样,设置

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React-virtualized是一个非常流行的React库,它可以帮助我们实现大数据量的虚拟滚动效果。下面是一个使用React-virtualized实现虚拟滚动的表格的示例代码: 首先,我们需要安装React-virtualized库: ``` npm install react-virtualized --save ``` 然后,我们需要引入Table和Column组件: ``` import { Table, Column } from 'react-virtualized'; ``` 接下来,我们可以定义一个数据源,例如: ``` const list = [ { name: '张三', age: '18', address: '北京市海淀区' }, { name: '李四', age: '20', address: '北京市朝阳区' }, { name: '王五', age: '22', address: '北京市西城区' }, // ... // 这里可以添加更多的数据 ]; ``` 然后,我们可以定义一个Table组件,指定它的rowCount和rowGetter属性: ``` <Table rowCount={list.length} rowGetter={({ index }) => list[index]} > ``` 接下来,我们可以添加一些Column组件,定义每一列的属性: ``` <Column label="姓名" dataKey="name" width={100} /> <Column label="年龄" dataKey="age" width={100} /> <Column label="地址" dataKey="address" width={200} /> ``` 最后,我们需要在Table组件中添加一些属性,以启用虚拟滚动: ``` <Table rowCount={list.length} rowGetter={({ index }) => list[index]} headerHeight={20} rowHeight={30} width={600} height={400} > ``` 在上面的代码中,我们设置了headerHeight和rowHeight属性来指定表头和每一行的高度,width和height属性用于指定表格的宽度和高度。React-virtualized会自动根据这些属性来计算出需要渲染的行数,并且只渲染当前可见的行,以实现虚拟滚动的效果。 完整的代码示例: ``` import React, { Component } from 'react'; import { Table, Column } from 'react-virtualized'; const list = [ { name: '张三', age: '18', address: '北京市海淀区' }, { name: '李四', age: '20', address: '北京市朝阳区' }, { name: '王五', age: '22', address: '北京市西城区' }, // ... // 这里可以添加更多的数据 ]; class VirtualTable extends Component { render() { return ( <Table rowCount={list.length} rowGetter={({ index }) => list[index]} headerHeight={20} rowHeight={30} width={600} height={400} > <Column label="姓名" dataKey="name" width={100} /> <Column label="年龄" dataKey="age" width={100} /> <Column label="地址" dataKey="address" width={200} /> </Table> ); } } export default VirtualTable; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值