前端进阶: 长列表渲染性能优化

2,248 阅读4分钟

为什么要对长列表进行优化

在处理大量数据时,如果不进行优化,会导致以下问题:

  1. 性能问题:在浏览器中渲染大量数据会导致性能问题,特别是在移动设备上更为明显。如果数据量很大,那么浏览器可能会变得非常慢或甚至崩溃。
  2. 用户体验问题:用户很难处理大量数据,如果列表中有很多条目,用户可能需要进行大量滚动才能找到他们想要的内容。这会导致用户感到疲劳和不满意。

性能问题的原因

性能问题通常是由于大量数据的处理和渲染引起的。在前端中,当我们需要渲染大量数据时,浏览器必须完成以下任务:

  1. 创建 DOM 元素:浏览器需要为每个数据项创建 DOM 元素,这可能是非常昂贵的。
  2. 布局和绘制:浏览器需要计算每个元素的位置,并将它们绘制在屏幕上。这也是一个昂贵的操作。
  3. 数据处理:前端代码必须对数据进行处理,以便渲染它们。这可能涉及大量的计算和逻辑运算,特别是在复杂的列表中。
  4. 网络请求:在从服务器加载数据时,网络请求可能会导致延迟和性能问题。

所有这些操作都需要浏览器进行处理,而浏览器的性能受到多种因素的影响,如处理器速度、内存大小和可用带宽等。如果数据量非常大,这些操作可能会变得非常耗时和耗能,从而导致性能问题。本文主要探求与 DOM 相关的优化思路。

为什么说 DOM 元素的开销非常昂贵呢? 是因为操作 DOM 元素会触发浏览器的重绘机制

重绘(Repaint)

优化方案

虚拟列表是一种用于优化长列表渲染性能的技术。它通过只渲染视窗内可见的元素,而不是所有的元素来提高渲染效率。当用户滚动列表时,虚拟列表会根据滚动位置动态地渲染新的元素,并删除不再可见的元素,从而减少DOM操作和重新渲染的次数,从而提高性能。

以下是虚拟列表的实现方式,可以优化页面性能:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Virtualized List</title>
  <style>
    #viewport {
      height: 400px;
      overflow-y: scroll;
      position: relative;
    }

    #list {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
    }

    #scrollbar {
      position: absolute;
      right: 0;
      top: 0;
      bottom: 0;
      width: 10px;
      background-color: #ccc;
      opacity: 0.5;
      border-radius: 5px;
    }
  </style>
</head>

<body>
  <div id="viewport">
    <div id="list"></div>
    <div id="scrollbar"></div>
  </div>
</body>
<script>
  // 虚拟列表的数据源
  const data = Array.from({ length: 100000 }, (_, index) => index + 1);
  // 单个列表项的高度
  const ITEM_HEIGHT = 30;
  // 可视区域的高度
  const VIEWPORT_HEIGHT = 400;
  // 可视区域中同时显示的列表项数量
  const VISIBLE_ITEMS_COUNT = Math.ceil(VIEWPORT_HEIGHT / ITEM_HEIGHT);
  // 虚拟滚动条的高度
  const SCROLLBAR_HEIGHT = VIEWPORT_HEIGHT * VIEWPORT_HEIGHT / data.length;
  // 生成虚拟列表的DOM结构
  const listEl = document.getElementById('list');
  listEl.style.height = `${ITEM_HEIGHT * data.length}px`;
  listEl.innerHTML = data.map((item) => `<div>${item}</div>`).join('');

  // 生成虚拟滚动条的DOM结构
  const scrollbarEl = document.getElementById('scrollbar');
  scrollbarEl.style.height = `${SCROLLBAR_HEIGHT}px`;

  // 使用防抖技术,监听滚动事件,更新列表和滚动条的位置
  const viewportEl = document.getElementById('viewport');

  // 隐藏滚动条
  viewportEl.addEventListener('mouseleave', () => {
    scrollbarEl.style.opacity = 0.5;
  });


  // 根据需要显示的列表项,更新DOM元素的内容
  viewportEl.addEventListener('scroll', () => {
    const scrollTop = viewportEl.scrollTop;
    const startIndex = Math.floor(scrollTop / ITEM_HEIGHT);
    const endIndex = Math.min(startIndex + VISIBLE_ITEMS_COUNT, data.length);
    listEl.style.transform = `translateY(${startIndex * ITEM_HEIGHT}px)`;
    scrollbarEl.style.top = `${scrollTop * VIEWPORT_HEIGHT / data.length}px`;
    scrollbarEl.style.opacity = 1;
    for (let i = startIndex; i < endIndex; i++) {
      const itemEl = listEl.children[i];
      if (!itemEl) {
        break;
      }
      itemEl.innerText = data[i];
    }
  });

</script>
</html>

Vue3推荐类库

Vue 3中有一个名为<virtual-scroller>的组件,它可以用于渲染大量数据的长列表,并且能够在性能和渲染效果之间找到一个平衡点。

<virtual-scroller>可以实现以下优化:

  1. 仅渲染可见区域内的元素,可以大大减少DOM操作,提高渲染性能;
  2. 支持缓存元素,避免重复创建DOM节点,提高渲染性能;
  3. 可以动态计算元素高度,避免静态设置固定高度时出现的问题;
  4. 支持动态调整列表高度,可以自适应容器大小变化。

以下是一个使用<virtual-scroller>组件的示例代码:

<template>
  <virtual-scroller
    :items="items"
    :item-height="itemHeight"
    :total-items="totalItems"
    :overscan-count="overscanCount"
    :key-field="keyField"
    @range-change="onRangeChange"
  >
    <template #default="{ item }">
      <div class="list-item">
        {{ item }}
      </div>
    </template>
  </virtual-scroller>
</template>

<script>
import { ref } from 'vue';
import VirtualScroller from 'vue3-virtual-scroller';

export default {
  components: {
    VirtualScroller,
  },
  setup() {
    const items = ref(Array.from({ length: 10000 }, (_, i) => i + 1));
    const itemHeight = ref(50);
    const totalItems = ref(items.value.length);
    const overscanCount = ref(10);
    const keyField = ref('id');

    function onRangeChange(range) {
      console.log('Visible range:', range.startIndex, '-', range.endIndex);
    }

    return {
      items,
      itemHeight,
      totalItems,
      overscanCount,
      keyField,
      onRangeChange,
    };
  },
};

在上面的代码中,我们首先引入了vue3-virtual-scroller库中的VirtualScroller组件,并将其注册为本组件的子组件。

然后,我们使用ref函数定义了一些响应式变量,包括:

  • items:需要渲染的数据列表;
  • itemHeight:列表项的高度;
  • totalItems:数据列表的总长度;
  • overscanCount:超出可见区域的缓冲项数;
  • keyField:用于识别每个数据项的键名。

接着,在template中,我们使用VirtualScroller组件,并传入上述响应式变量作为参数。我们还为组件的默认插槽提供了一个模板,用于渲染每个数据项。

最后,在setup函数中,我们定义了一个onRangeChange函数,用于处理range-change事件,该事件在可见区域发生变化时触发。我们将该函数绑定到组件的range-change事件上,并在控制台中打印出当前可见区域的范围。

总结

虚拟列表是一种优化长列表渲染性能的有效技术,通过动态渲染可视区域内的元素,可以大大减少DOM操作和重新渲染的次数,从而提高性能。

参考文献

vue3-virtual-scroller