首页 短视频

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验

分类:短视频
字数: (6289)
阅读: (1433)
内容摘要:Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验,

在面对海量数据渲染时,Vue 的性能瓶颈会凸显出来,尤其是长列表渲染。如果直接将所有数据一次性渲染到页面上,会导致浏览器卡顿、响应缓慢,严重影响用户体验。这时,Vue 虚拟列表技术就派上了用场。它的核心思想是只渲染可见区域的内容,而不是一次性渲染整个列表,从而大幅提升渲染性能。本文将深入探讨三种常见的 Vue 虚拟列表实现方案,并进行详细对比与实践。

问题场景重现:十万条数据的噩梦

假设我们需要渲染一个包含十万条数据的列表。如果直接使用 v-for 循环渲染,代码如下:

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验
<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
    }
  }
}
</script>

这段代码在数据量较小的时候可能没有问题,但是当数据量达到十万条时,浏览器会明显卡顿,甚至崩溃。这是因为 Vue 需要创建大量的 DOM 节点,并进行大量的计算和渲染操作。如果我们使用了 Nginx 做静态资源服务器,发现 CPU 瞬间跑满,这表明大量的计算资源被消耗在了前端渲染上,后端服务都无法正常响应用户的请求了。

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验

虚拟列表核心原理剖析

虚拟列表的核心原理是只渲染可视区域内的列表项,当滚动条滚动时,动态更新可视区域内的列表项。它主要涉及以下几个关键概念:

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验
  • 可视区域高度: 浏览器窗口中可见的列表区域的高度。
  • 列表项高度: 每个列表项的高度,可以固定也可以动态计算。
  • 起始索引: 可视区域内第一个列表项在整个列表中的索引。
  • 结束索引: 可视区域内最后一个列表项在整个列表中的索引。
  • 偏移量: 可视区域顶部相对于整个列表顶部的偏移量,用于实现滚动效果。

通过计算起始索引和结束索引,我们可以只渲染 items.slice(startIndex, endIndex) 的数据,从而大大减少了 DOM 节点的数量,提升了渲染性能。

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验

三种 Vue 虚拟列表实现方案对比

1. 基于 ElementUI 的 el-virtual-scroll 组件

ElementUI 提供了 el-virtual-scroll 组件,可以很方便地实现虚拟列表。它内部已经封装好了虚拟列表的逻辑,我们只需要配置一些参数即可。这种方案的优点是使用简单,快速上手,但是灵活性较低,定制化程度不高。例如,如果我们需要自定义列表项的样式或者添加一些额外的交互,可能需要修改 ElementUI 的源代码。如果使用了宝塔面板,我们可以很方便地安装 Nginx 来优化静态资源的访问。

<template>
  <el-virtual-scroll
    :height="400"
    :item-size="50"
    :items="items"
  >
    <template #default="{ item }">
      <div>{{ item.name }}</div>
    </template>
  </el-virtual-scroll>
</template>

<script>
import { ElVirtualScroll } from 'element-plus'
export default {
  components: { ElVirtualScroll },
  data() {
    return {
      items: Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
    }
  }
}
</script>

2. 基于 IntersectionObserver API 的方案

IntersectionObserver API 可以监听元素是否进入可视区域。我们可以利用这个 API 来动态加载列表项。这种方案的优点是灵活性较高,可以自定义加载策略,但是实现起来相对复杂。需要手动计算起始索引、结束索引和偏移量。

<template>
  <div class="list-container" ref="listContainer" @scroll="handleScroll">
    <div class="list-phantom" :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="list-item"
      v-for="item in visibleItems"
      :key="item.id"
      :style="{ top: item.top + 'px' }"
    >
      {{ item.name }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
      itemHeight: 50, // 假设列表项高度为 50px
      visibleCount: 20, // 可视区域内最多显示的列表项数量
      startIndex: 0, // 起始索引
      endIndex: 20, // 结束索引
      visibleItems: [], // 可视区域内的列表项
    }
  },
  computed: {
    totalHeight() {
      return this.items.length * this.itemHeight
    }
  },
  mounted() {
    this.updateVisibleItems()
  },
  methods: {
    handleScroll() {
      const scrollTop = this.$refs.listContainer.scrollTop
      this.startIndex = Math.floor(scrollTop / this.itemHeight)
      this.endIndex = this.startIndex + this.visibleCount
      this.updateVisibleItems()
    },
    updateVisibleItems() {
      this.visibleItems = this.items.slice(this.startIndex, this.endIndex).map((item, index) => ({
        ...item,
        top: (this.startIndex + index) * this.itemHeight
      }))
    }
  }
}
</script>

<style scoped>
.list-container {
  height: 400px;
  overflow-y: auto;
  position: relative;
}

.list-phantom {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
}

.list-item {
  position: absolute;
  left: 0;
  width: 100%;
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #eee;
  box-sizing: border-box;
}
</style>

3. 基于 Vue Composition API 的方案

Vue Composition API 提供了一种更加灵活和可组合的方式来管理组件的状态和逻辑。我们可以将虚拟列表的逻辑封装成一个独立的 Composition 函数,然后在组件中引入和使用。这种方案的优点是代码可读性高,易于维护和测试。

// useVirtualList.js
import { ref, computed, onMounted } from 'vue'

export function useVirtualList(items, itemHeight) {
  const listContainer = ref(null)
  const startIndex = ref(0)
  const visibleCount = ref(20)
  const visibleItems = ref([])

  const totalHeight = computed(() => items.length * itemHeight)
  const endIndex = computed(() => startIndex.value + visibleCount.value)

  const updateVisibleItems = () => {
    visibleItems.value = items.slice(startIndex.value, endIndex.value).map((item, index) => ({
      ...item,
      top: (startIndex.value + index) * itemHeight
    }))
  }

  const handleScroll = () => {
    if (listContainer.value) {
      const scrollTop = listContainer.value.scrollTop
      startIndex.value = Math.floor(scrollTop / itemHeight)
      updateVisibleItems()
    }
  }

  onMounted(() => {
    updateVisibleItems()
  })

  return {
    listContainer,
    totalHeight,
    visibleItems,
    handleScroll
  }
}

// MyComponent.vue
<template>
  <div class="list-container" ref="listContainer" @scroll="handleScroll">
    <div class="list-phantom" :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="list-item"
      v-for="item in visibleItems"
      :key="item.id"
      :style="{ top: item.top + 'px' }"
    >
      {{ item.name }}
    </div>
  </div>
</template>

<script>
import { useVirtualList } from './useVirtualList'

export default {
  setup() {
    const items = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
    const itemHeight = 50

    const { listContainer, totalHeight, visibleItems, handleScroll } = useVirtualList(items, itemHeight)

    return {
      listContainer,
      totalHeight,
      visibleItems,
      handleScroll
    }
  }
}
</script>

<style scoped>
.list-container {
  height: 400px;
  overflow-y: auto;
  position: relative;
}

.list-phantom {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
}

.list-item {
  position: absolute;
  left: 0;
  width: 100%;
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #eee;
  box-sizing: border-box;
}
</style>

实战避坑经验总结

  • 列表项高度的计算: 尽量使用固定高度的列表项,如果列表项高度不固定,需要动态计算高度,这会增加计算量,降低性能。可以使用 ResizeObserver API 来监听列表项高度的变化。
  • 滚动事件的优化: 滚动事件触发频率很高,需要进行节流或者防抖处理,避免频繁更新可视区域,如果前端需要高并发连接,Nginx 的配置也是关键,需要调整 worker_processesworker_connections 参数。
  • 性能测试: 在实际项目中,需要进行性能测试,评估虚拟列表的性能提升效果,选择最合适的方案。可以使用 Chrome DevTools 的 Performance 面板来分析性能瓶颈。尤其要注意内存占用,避免内存泄漏。
  • 滚动条样式: 不同的浏览器对滚动条的样式支持不同,需要进行兼容性处理。可以使用 CSS 来自定义滚动条的样式,例如使用 ::-webkit-scrollbar 来修改 Chrome 浏览器的滚动条样式。

掌握 Vue 虚拟列表技术,能够有效地解决长列表渲染的性能问题,提升用户体验。在实际项目中,需要根据具体情况选择最合适的方案,并进行优化和调整。

Vue 虚拟列表:性能优化三剑客,告别卡顿丝滑体验

转载请注明出处: 加班到秃头

本文的链接地址: http://m.acea1.store/blog/238015.SHTML

本文最后 发布于2026-04-24 02:47:04,已经过了3天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 煎饼果子 1 天前
    第三种 Composition API 的写法很优雅,代码结构清晰,学习了!
  • 咸鱼翻身 5 天前
    讲得真详细,连 Nginx 都考虑到了,大佬就是大佬!
  • 吃瓜群众 5 天前
    第三种 Composition API 的写法很优雅,代码结构清晰,学习了!
  • 红豆沙 12 小时前
    ElementUI 的 el-virtual-scroll 用起来确实方便,但是定制化程度不高,有点鸡肋。
  • 网瘾少年 3 天前
    ElementUI 的 el-virtual-scroll 用起来确实方便,但是定制化程度不高,有点鸡肋。