如何实现虚拟滚动
(本文使用 Vue 3 编写)
当我们面对大量数据时,传统的渲染方式会导致浏览器负载过大,页面渲染速度慢,滚动卡顿等问题。为此,虚拟滚动技术成为一种有效的性能优化手段。本文将介绍如何在 Vue 3 中实现虚拟滚动。
目录
- 传统渲染方式
- 虚拟滚动的原理
- 虚拟滚动的实现(元素固定高度)
- 使用工具(
vue3-virtual-scroll-list
)
传统渲染方式
假设我们要渲染 10 万条数据,传统的渲染方式如下:
<template>
<div class="parent-box">
<div v-for="_, index in listData" class="list-item">
{{ `item - ${index + 1}` }}
</div>
</div>
</template>
<script setup>
const listData = new Array(100000)
</script>
问题:一次性渲染 10 万条数据会给浏览器带来极大的压力,导致页面滚动卡顿甚至崩溃。
虚拟滚动的原理
虚拟滚动通过减少一次性渲染的 DOM 数量来优化性能,核心原理包括:
- 计算容器高度:计算可视区域高度。
- 创建占位元素:通过创建等于总数据高度的占位元素撑开滚动条,达到虚拟滚动效果。
- 确定可容纳的数据条数:根据容器高度和单个元素高度计算可视区域内显示的条数。
- 监听滚动事件:当滚动时,计算当前滚动位置并渲染对应的数据。
- 渲染可视区域数据:只渲染可视区域内的数据,避免一次性渲染所有数据。
虚拟滚动的实现(元素固定高度)
实现固定高度的虚拟滚动如下:
<template>
<div class="parent-box" @scroll="getVisibleData">
<div class="position-total"></div>
<div class="list-item-box">
<div v-for="value in visibleData" class="list-item">
{{ `item - ${value}` }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const lineHeight = 25
const totalData = Array.from({ length: 100000 }, (_, index) => index + 1)
const visibleData = ref([])
onMounted(() => {
getVisibleData()
})
const getVisibleData = () => {
const parentBox = document.querySelector('.parent-box')
const visibleDataCount = Math.ceil(parentBox.clientHeight / lineHeight)
const startIndex = Math.floor(parentBox.scrollTop / lineHeight)
const listItemBox = document.querySelector('.list-item-box')
listItemBox.style.top = (startIndex * lineHeight) + 'px'
visibleData.value = totalData.slice(startIndex, startIndex + visibleDataCount)
}
const lineHeightPx = lineHeight + 'px'
const totalHeightPx = lineHeight * totalData.length + 'px'
</script>
<style lang="scss" scoped>
.parent-box {
position: relative;
height: 400px;
width: 300px;
overflow: auto;
border: 2px solid rgb(87, 87, 87);
.position-total {
height: v-bind(totalHeightPx);
}
.list-item-box {
position: absolute;
width: 100%;
.list-item {
height: v-bind(lineHeightPx);
border-bottom: 1px solid rgb(223, 223, 223);
}
}
}
</style>
说明:
parent-box
:容器用于监听滚动事件。list-item-box
:虚拟滚动中实际显示的元素。- 当用户滚动时,只渲染可视区域内的数据,并调整
list-item-box
的top
值来更新滚动位置。
使用工具(vue3-virtual-scroll-list
)
对于更复杂的需求,可以使用现成的虚拟滚动工具,比如 vue3-virtual-scroll-list
。
引入
import VueVirtualScroller from 'vue3-virtual-scroll-list'
import App from './App.vue'
const app = createApp(App)
app.component('vue-virtual-scroller', VueVirtualScroller).mount('#app')
使用
<template>
<div class="parent-box">
<vue-virtual-scroller
style="height: 100%; overflow-y: auto;"
data-key="id"
:data-sources="totalData"
:data-component="ListItem"
:keeps="20"
/>
</div>
</template>
<script setup>
import ListItem from './listItem.vue'
const totalData = Array.from({ length: 100000 }, (_, index) => ({ id: index + 1 }))
</script>
<style lang="scss" scoped>
.parent-box {
position: relative;
height: 400px;
width: 300px;
border: 2px solid rgb(87, 87, 87);
}
</style>
ListItem 组件
<template>
<div class="list-item">
{{ `item - ${source.id}` }}
</div>
</template>
<script setup>
defineOptions({ name: 'ListItem' })
const props = defineProps({
source: {
type: Object,
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
.list-item {
border-bottom: 1px solid rgb(223, 223, 223);
}
</style>
说明:
vue-virtual-scroller
提供了简单高效的虚拟滚动实现,只需提供数据源和子组件即可轻松实现虚拟滚动效果。ListItem
子组件用于展示每一条数据。
结语
虚拟滚动是前端性能优化的利器,特别是在大量数据渲染时,通过减少一次性渲染的 DOM 数量,显著提升了页面的性能。可以根据具体项目需求选择手动实现或使用现成的工具来优化数据渲染的效率。