好家伙,
1. 什么是虚拟列表
虚拟列表(Virtual List)是一种优化长列表渲染性能的技术。当我们需要展示成千上万条数据时,如果一次性将所有数据渲染到DOM中,会导致页面卡顿甚至崩溃。虚拟列表的核心思想是:只渲染可视区域内的数据,而不是渲染所有数据
2. 使用场景
虚拟列表适用于以下场景:
- 大数据量展示:如聊天记录、新闻列表、商品列表等需要展示大量数据的场景
- 无限滚动:需要支持用户持续滚动加载更多内容的场景
- 性能敏感:在低性能设备上运行的应用,需要尽可能减少DOM操作
- 实时数据更新:频繁更新的数据列表,如股票行情、实时监控数据等
(我觉得实际场景中,分页会用到更多,用户要看的数据,永远只是一小部分,就那么几条,找不到就用搜索
但总要学学)
3. 虚拟列表原理
一句话:
要看了,再渲染
对,就这么简单,下面,进行分步
- 计算可视区域:确定用户当前可以看到的视口范围
- 计算可见项:根据视口位置、每项高度,计算出当前应该显示哪些数据项
- 渲染可见项:只渲染计算出的可见项到DOM中
- 位置偏移:通过CSS定位,确保可见项在正确的位置显示
- 监听滚动:当用户滚动时,重新计算可见项并更新DOM
这里几个难点:
我怎么知道哪些数据进入了可视区域?
答:监听滚动距离,滚到哪,就从哪里开始
4. 实现虚拟列表
Demo.html代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生JavaScript虚拟列表实现</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.list-container {
position: relative;
height: 400px;
overflow: auto;
border: 1px solid #ccc;
margin: 20px auto;
width: 80%;
}
.list-phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.list-content {
position: absolute;
left: 0;
right: 0;
top: 0;
overflow: hidden;
}
.list-item {
padding: 10px;
border-bottom: 1px solid #eee;
color: #666;
}
.list-item:hover {
background-color: #f5f5f5;
}
</style>
</head>
<body>
<h1 style="text-align: center; margin: 20px 0;">原生JavaScript虚拟列表</h1>
<div id="virtualList" class="list-container">
<div class="list-phantom"></div>
<div class="list-content"></div>
</div>
<script>
class VirtualList {
constructor(options) {
this.container = options.container;
this.data = options.data || [];
this.itemHeight = options.itemHeight || 50;
this.bufferSize = options.bufferSize || 5;
this.phantom = this.container.querySelector('.list-phantom');
this.content = this.container.querySelector('.list-content');
this.startIndex = 0;
this.endIndex = 0;
this.scrollTop = 0;
this.init();
}
init() {
// 设置占位容器的高度
this.phantom.style.height = this.data.length * this.itemHeight + 'px';
// 监听滚动事件
this.container.addEventListener('scroll', this.handleScroll.bind(this));
// 初始渲染
this.updateVisibleItems();
}
handleScroll() {
// 获取当前滚动位置
this.scrollTop = this.container.scrollTop;
// 更新可见项
this.updateVisibleItems();
}
updateVisibleItems() {
// 计算开始和结束索引
this.startIndex = Math.floor(this.scrollTop / this.itemHeight);
this.endIndex = this.startIndex + Math.ceil(this.container.clientHeight / this.itemHeight);
// 添加缓冲区
this.startIndex = Math.max(0, this.startIndex - this.bufferSize);
this.endIndex = Math.min(this.data.length, this.endIndex + this.bufferSize);
// 计算偏移量
const offsetY = this.startIndex * this.itemHeight;
// 设置内容容器的偏移
this.content.style.transform = `translateY(${offsetY}px)`;
// 渲染可见项
this.renderItems();
}
renderItems() {
// 清空内容容器
this.content.innerHTML = '';
// 渲染可见项
for (let i = this.startIndex; i < this.endIndex; i++) {
const item = document.createElement('div');
item.className = 'list-item';
item.innerHTML = this.renderItemContent(this.data[i], i);
item.style.height = this.itemHeight + 'px';
this.content.appendChild(item);
}
}
renderItemContent(item, index) {
return `<div>索引: ${index}, 内容: ${item}</div>`;
}
}
// 生成测试数据
const data = Array.from({ length: 10000 }, (_, i) => `列表项 ${i + 1}`);
// 初始化虚拟列表
const virtualList = new VirtualList({
container: document.getElementById('virtualList'),
data: data,
itemHeight: 50,
bufferSize: 10
});
</script>
</body>
</html>
5.最后总结
为什么滚动到指定位置后会将对应区域数据渲染?
1.监听滚动事件
2.滚动触发数据更新方法
3.根据滚动距离计算当前数据索引
4.根据可视区域计算要渲染数据项
5.渲染数据
6.定位内容