Skip to content

一文搞懂虚拟列表

1.虚拟列表介绍

虚拟列表是一种用于优化大型列表渲染性能的技术。当你需要展示大量数据时,例如聊天记录、商品列表等,传统的渲染方式可能会导致性能问题,因为要同时渲染大量的DOM元素。

虚拟列表通过只渲染可见区域内的元素,从而减少DOM操作,提高渲染性能。以下是虚拟列表的基本工作原理和一些实现步骤:

  1. 确定容器高度: 首先,确定列表容器的高度,这将决定可见区域的大小。
  2. 计算可见元素: 基于容器高度,计算出可见区域内可以容纳的元素数量。这些元素将是实际渲染的元素。
  3. 渲染可见元素: 仅渲染可见区域内的元素,而不是整个列表。未在可见区域内的元素不会被创建或渲染。
  4. 虚拟滚动: 监听滚动事件,根据滚动位置动态更新可见区域内的元素。只有当用户滚动列表时,才进行实际的DOM操作。

人话描述:初次渲染一屏的dom项,然后每次滚动都细粒度到一项一项去渲染

思路:监听滚动事件,每一次滚动的时候都动态计算startIndex(起始索引) 和 startIndex(起始索引) + visibleItems(展示区可视区域可以容纳的项数)的值,然后可以利用v-for或者for循环去渲染!

以下是一个简单的虚拟列表实现示例,使用 JavaScript 和 HTML/CSS。在这个示例中,我们将使用一个包含大量数据的列表,并通过虚拟列表来优化渲染性能:

HTML:

html
<div class="container">
  <ul class="list">
    <!-- 这里不需要在初始状态渲染所有元素 -->
  </ul>
</div>

CSS:

css
.container {
  height: 300px;
  overflow: auto;
}

.list {
  padding: 0;
  margin: 0;
}

JavaScript:

js
const container = document.querySelector('.container');
const list = document.querySelector('.list');
const itemHeight = 50; //每一项的高度
const totalItems = 1000; //总项数
const visibleItems = Math.ceil(container.clientHeight / itemHeight); //计算展示区可视区域可以容纳的项数(就是默认的基本渲染个数)

function renderItems(startIndex) {
  const fragment = document.createDocumentFragment();
  for (let i = startIndex; i < startIndex + visibleItems; i++) {
    const item = document.createElement('li'); //创建dom,只创建和渲染对应需要展示的一部分,而不是已经加载的也去渲染
    item.textContent = `Item ${i}`;
    item.style.height = `${itemHeight}px`;
    fragment.appendChild(item);
  }
  list.innerHTML = '';
  list.appendChild(fragment);
}

container.addEventListener('scroll', () => {
  const scrollTop = container.scrollTop;
  const startIndex = Math.floor(scrollTop / itemHeight); //计算当前应该渲染第几项了(利用滚动的距离除以每个项的高度)
  renderItems(startIndex); //只有滚动到相应的位置才渲染
});

// 初始渲染
renderItems(0); //0+默认的基本渲染个数

这个示例展示了如何创建一个基本的虚拟列表,当用户滚动列表时,它会动态地渲染可见区域内的元素,而不是一次性渲染所有元素。这可以有效地提高大型列表的性能,因为只有可见区域内的元素才会被渲染和操作。在实际应用中,还可以结合一些性能优化技巧,如对象池、动态加载等,以进一步提升虚拟列表的性能。

由于只渲染当前能看到的元素,理论上会出现没有滚动条的情况,那么就意味着无法监听滚动事件,所以我们如何把滚动条撑开呢?

在前面和后面都加一个空的div即可!把高度撑开,让内容区域的高度大于容器区域,就可以出现滚动条了!

2.虚拟列表展示和触底加载的区别?

虚拟列表展示和触底加载是两种不同的前端技术,用于处理大量数据的列表渲染和加载。它们的主要区别在于它们解决的问题和实现的方式。

  1. 虚拟列表展示: 虚拟列表展示是一种用于优化大型列表渲染性能的技术。当你需要展示大量数据时,传统的渲染方式可能会导致性能问题,因为要同时渲染大量的DOM元素。虚拟列表通过只渲染可见区域内的元素,从而减少DOM操作,提高渲染性能。这种技术通常会根据可见区域的大小和滚动位置,动态地渲染列表的子集,从而保持页面的性能。——> 相对于触底加载,最大的好处是每次滚动都细粒度到一项一项去渲染,并且只创建和渲染对应需要展示的一部分,而不是已经加载的也去渲染,而触底加载不行!其他的好处可能是不需要每次滚动都进行等待(因为按理来说也是一次性请求所有数据),而且还能优化加载速度
  2. 触底加载: 触底加载是一种处理无限滚动列表的方式,通常用于加载更多数据。当用户滚动到列表的底部时,会触发加载更多数据的操作,以实现无缝地加载更多内容,而不需要显式地点击“加载更多”按钮或进行其他操作。这对于需要加载大量数据的应用,如社交媒体的滚动式新闻加载、无限滚动列表等,非常有用。

**虚拟列表展示和触底加载通常可以结合使用,以实现更好的用户体验。**例如,在一个虚拟列表中,你可以使用触底加载来加载更多数据,从而逐步加载用户滚动到的下一页内容,而不是一次性加载所有数据。

综上所述,虚拟列表展示主要是为了优化大型列表的渲染性能,而触底加载主要用于处理无限滚动列表并实现动态加载数据。两者都是为了提高用户体验和减少不必要的性能开销。

3.在vue里面实现虚拟列表展示

在 Vue 中实现虚拟列表展示通常涉及以下步骤:

  1. 计算可见区域: 首先,你需要计算出可见区域内可以容纳多少个列表项,并计算每个列表项的高度。
  2. 创建虚拟列表容器: 创建一个固定高度的容器,用于显示可见区域内的列表项。这个容器可以使用 CSS 样式来设置高度和样式。
  3. 动态渲染列表项: 使用 v-for 指令渲染虚拟列表容器中的列表项。根据可见区域的高度和列表项的高度,动态计算要渲染的列表项数量和位置,并将其渲染到容器中。
  4. 监听滚动事件: 监听虚拟列表容器的滚动事件,以获取滚动位置,然后根据滚动位置动态计算可见区域内要渲染的列表项。

下面是一个简单的示例,演示了如何在 Vue 中实现虚拟列表展示:

vue
<template>
  <div class="virtual-list" ref="listContainer" @scroll="handleScroll">
    <div class="virtual-list-item" v-for="(item, index) in visibleItems" :key="index">
      {{ item }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      totalItems: 1000, // 总列表项数量
      itemHeight: 50,   // 列表项高度
      visibleItems: []  // 可见区域内的列表项
    };
  },
  methods: {
    handleScroll() {
      const container = this.$refs.listContainer;
      const scrollTop = container.scrollTop;
      const startIndex = Math.floor(scrollTop / this.itemHeight);
      this.updateVisibleItems(startIndex);
    },
    updateVisibleItems(startIndex) {
      const endIndex = Math.min(startIndex + this.visibleItemCount, this.totalItems); //取totalItems和总共的其中的较小的,防止总数不够
      this.visibleItems = this.allItems.slice(startIndex, endIndex); //滚动到相应位置后,从所有数据中截取一部分(只渲染对应需要展示的一部分,而不是已经加载的也去渲染),作为visibleItems来v-for渲染
    }
  },
  computed: {
    visibleItemCount() {
      return Math.ceil(this.$refs.listContainer.clientHeight / this.itemHeight);
    },
    allItems() {
      // 生成所有列表项的数据,例如使用循环生成,实际应该是从后端ajax请求
      return Array.from({ length: this.totalItems }, (_, index) => `Item ${index}`);
    }
  },
  mounted() {
    this.updateVisibleItems(0); // 初始渲染
  }
};
</script>

<style>
.virtual-list {
  height: 300px;
  overflow: auto;
}

.virtual-list-item {
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #ccc;
  text-align: center;
}
</style>

在这个示例中,我们创建了一个虚拟列表容器,根据滚动位置动态计算可见区域内要渲染的列表项,并将其渲染到容器中。这是一个基本的示例,你可以根据实际需求进行定制和优化。