📖 简介
vue-virtual-scroller 是一个专为 Vue.js 应用设计的高性能虚拟滚动组件库。它通过”虚拟化”技术解决了在 Vue 应用中渲染大量数据时的性能问题,只渲染用户当前可见的元素,而不是整个列表,从而显著提高了渲染性能和内存使用效率。
🎯 核心特性
- 🚀 极致性能:只渲染可视区域内的元素,支持数万条数据的流畅滚动
- 💾 内存优化:显著降低 DOM 节点数量,减少内存占用
- 🎨 高度定制:灵活的样式和交互定制能力
- 📱 响应式:完美适配各种屏幕尺寸
- 🔄 动态高度:支持固定高度和动态高度两种模式
- ⚡ 平滑滚动:优化的滚动体验,支持自动滚动到指定位置
- 🔧 易于集成:简单的 API 设计,快速上手
🆚 与其他虚拟滚动库对比
特性 | vue-virtual-scroller | react-window | react-virtualized |
---|---|---|---|
框架支持 | Vue 2/3 | React | React |
体积 | 轻量级 | 轻量级 | 较重 |
API 简洁性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
性能表现 | 优秀 | 优秀 | 优秀 |
文档完整性 | 完整 | 完整 | 完整 |
📦 安装
使用 npm
npm install vue-virtual-scroller@next
🚀 快速开始
基础用法
<template>
<div class="virtual-list-container">
<RecycleScroller
class="scroller"
:items="items"
:item-size="50"
key-field="id"
>
<template #default="{ item }">
<div class="list-item">
{{ item.name }}
</div>
</template>
</RecycleScroller>
</div>
</template>
<script>
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
export default {
name: "VirtualListExample",
components: {
RecycleScroller,
},
data() {
return {
items: Array.from({ length: 10000 }, (_, index) => ({
id: index,
name: `Item ${index}`,
})),
};
},
};
</script>
<style scoped>
.virtual-list-container {
height: 400px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.scroller {
height: 100%;
}
.list-item {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
background: white;
transition: background-color 0.2s;
}
.list-item:hover {
background-color: #f5f5f5;
}
</style>

🎨 组件详解
RecycleScroller
RecycleScroller
是主要的虚拟滚动组件,适用于固定高度的列表项。
Props
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
items | Array | [] | 要渲染的数据数组 |
itemSize | Number | 50 | 每个列表项的高度(像素) |
keyField | String | ‘id’ | 用于唯一标识每个项目的字段名 |
pageMode | Boolean | false | 是否启用页面模式 |
prerender | Number | 5 | 预渲染的项目数量 |
buffer | Number | 200 | 缓冲区大小(像素) |
minItemSize | Number | – | 最小项目高度 |
sizeField | String | – | 动态高度字段名 |
typeField | String | – | 项目类型字段名 |
Events
事件名 | 参数 | 说明 |
---|---|---|
scroll | { event, scrollTop, scrollLeft } | 滚动事件 |
resize | { size } | 容器大小变化事件 |
visible | { startIndex, endIndex } | 可见项目变化事件 |
DynamicScroller
DynamicScroller
适用于动态高度的列表项。
<template>
<div class="dynamic-list-container">
<DynamicScroller
class="scroller"
:items="items"
:min-item-size="50"
key-field="id"
>
<template #default="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.content]"
:watch-data="true"
>
<div class="dynamic-item">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
</div>
</template>
<script>
import { DynamicScroller, DynamicScrollerItem } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
export default {
name: "DynamicListExample",
components: {
DynamicScroller,
DynamicScrollerItem,
},
data() {
return {
items: Array.from({ length: 1000 }, (_, index) => ({
id: index,
title: `Item ${index}`,
content: `This is the content for item ${index}. ${"Lorem ipsum ".repeat(
Math.floor(Math.random() * 10) + 1
)}`,
})),
};
},
};
</script>
<style scoped>
.dynamic-list-container {
height: 500px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.scroller {
height: 100%;
}
.dynamic-item {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
background: white;
}
.dynamic-item h3 {
margin: 0 0 8px 0;
color: #333;
font-size: 16px;
font-weight: 600;
}
.dynamic-item p {
margin: 0;
color: #666;
line-height: 1.5;
}
</style>

🔧 高级用法
带选择功能的虚拟列表
<template>
<div class="selectable-list">
<div class="list-header">
<div class="selection-info">已选择 {{ selectedItems.length }} 个项目</div>
<button @click="selectAll" class="btn btn-primary">全选</button>
<button @click="clearSelection" class="btn btn-secondary">清除</button>
<button @click="scrollToBottom" class="btn btn-info">滚动到底部</button>
</div>
<RecycleScroller
ref="scroller"
class="scroller"
:items="items"
:item-size="50"
key-field="id"
>
<template #default="{ item }">
<div
class="list-item"
:class="{ selected: selectedItems.includes(item.id) }"
@click="toggleSelection(item.id)"
>
<input
type="checkbox"
:checked="selectedItems.includes(item.id)"
@change="toggleSelection(item.id)"
@click.stop
class="checkbox"
/>
<span class="item-name">{{ item.name }}</span>
<span class="item-status">{{ item.status }}</span>
</div>
</template>
</RecycleScroller>
</div>
</template>
<script>
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
export default {
name: "SelectableListExample",
components: {
RecycleScroller,
},
data() {
return {
selectedItems: [],
items: Array.from({ length: 5000 }, (_, index) => ({
id: index,
name: `Item ${index}`,
status:
index % 3 === 0 ? "活跃" : index % 3 === 1 ? "待处理" : "已完成",
})),
};
},
methods: {
toggleSelection(id) {
const index = this.selectedItems.indexOf(id);
if (index > -1) {
this.selectedItems.splice(index, 1);
} else {
this.selectedItems.push(id);
}
},
selectAll() {
this.selectedItems = this.items.map((item) => item.id);
},
clearSelection() {
this.selectedItems = [];
},
scrollToBottom() {
// 滚动到最后一个项目
this.$nextTick(() => {
if (this.$refs.scroller) {
this.$refs.scroller.scrollToItem(this.items.length - 1);
}
});
},
},
};
</script>
<style scoped>
.selectable-list {
height: 500px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.list-header {
padding: 12px 16px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
gap: 12px;
}
.selection-info {
flex: 1;
font-size: 14px;
color: #666;
}
.btn {
padding: 6px 12px;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover {
background: #138496;
}
.scroller {
flex: 1;
}
.list-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
background: white;
cursor: pointer;
transition: background-color 0.2s;
}
.list-item:hover {
background-color: #f5f5f5;
}
.list-item.selected {
background-color: #e3f2fd;
}
.checkbox {
margin-right: 12px;
}
.item-name {
flex: 1;
font-weight: 500;
color: #333;
}
.item-status {
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
background: #f0f0f0;
color: #666;
}
</style>

🎯 性能优化技巧
1. 使用 key 属性
确保每个列表项都有唯一的 key,这对于虚拟滚动的性能至关重要:
<RecycleScroller
:items="items"
:item-size="50"
key-field="id"
/>
2. 避免在模板中使用复杂计算
<!-- ❌ 错误做法 -->
<template #default="{ item }">
<div>{{ expensiveComputation(item) }}</div>
</template>
<!-- ✅ 正确做法 -->
<template #default="{ item }">
<div>{{ item.computedValue }}</div>
</template>
<script>
export default {
computed: {
items() {
return this.rawItems.map((item) => ({
...item,
computedValue: this.expensiveComputation(item),
}));
},
},
};
</script>
3. 使用 v-memo 优化渲染
<template #default="{ item }">
<div v-memo="[item.id, item.name]">
{{ item.name }}
</div>
</template>
4. 合理设置缓冲区大小
<RecycleScroller
:items="items"
:item-size="50"
:buffer="300"
key-field="id"
/>
5. 滑动到底部
scrollToBottom() {
// 滚动到最后一个项目
this.$nextTick(() => {
if (this.$refs.scroller) {
this.$refs.scroller.scrollToItem(this.items.length - 1);
}
});
}
🐛 常见问题
Q: 列表项高度不一致怎么办?
A: 使用 DynamicScroller
组件:
<DynamicScroller :items="items" :min-item-size="50" key-field="id">
<template #default="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.content]"
>
<div class="dynamic-item">
{{ item.content }}
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
原文链接:https://code.ifrontend.net/archives/945,转载请注明出处。
评论0