虚拟滚动列表基础原理篇
虚拟滚动列表听说已久,一直没有自己动手实现, 今天剖析一下基础原理,涉及缓存和优化请查看下一篇文章 虚拟滚动列表优化篇,下面直接进入正题。
1. 页面构成
- 外部固定高度的盒子
- 内部模拟全部列表元素高度的盒子
- 真实的显示区域
2. 需要注意问题
- 真实显示区域就是中间那块有元素的地方
- 需要让真实显示的区域不会滚动划走
- 滚动高度不是每个列表元素高度整数倍的处理
3. 具体实现
html 部分, 这里以vue为基础
<div id="app">
<!-- 外层盒子,固定高度 -->
<div class="container" :style="`height: ${boxH}px`" @scroll="handleScroll">
<!-- 内层滚动盒子, 高度是虚拟的数据的整体高度!!!, 这样使得滚动条更像真实的 -->
<div class="scroll-box" :style="`height: ${allHeight}px`">
<!-- 真正显示区域, 需要通过trasform 来让显示区域一直在屏幕中,而不是滑走 -->
<ul :style="`transform:translateY(${offsetY}px)`">
<li
v-for="item,index in nowList" :key="index"
:style="`height: ${itemH}px`"
class="list-item"
>{{item}}</li>
</ul>
</div>
</div>
</div>
css 部分
<style>
* {
padding: 0;
margin: 0;
}
.container {
overflow-y: auto;
box-sizing: border-box;
border: 1px solid black;
}
.list-item {
list-style: none;
border: 1px solid red;
}
</style>
js 部分代码
<script>
new Vue({
el:"#app",
data() {
return {
boxH: 700, // 外层盒子高度
itemH: 100, // 每个元素的高度
ListNum: 100, // 整体个数
list: [], // 列表整体数据
nowList: [], // 目前显示列表
offsetY: 0, // 显示区域动态偏移量
}
},
created() {
// 初始化第一页面的数据
this.init()
},
computed: {
allHeight() {
return this.ListNum * this.itemH
},
pageNum() {
return Math.ceil(this.boxH / this.itemH)
}
},
methods: {
init() {
// 1. 模拟整个列表元素
const list = []
for(let i = 0; i < this.ListNum; i++) {
list.push(i)
}
this.list = list
// 2. 取得当前第一页的显示数据
this.nowList = this.list.slice(0, this.pageNum + 1)
},
handleScroll(e) {
// e.target.scrollTop 卷起高度, 需注意滑动单个元素显示一半的情况
const scrollTop = e.target.scrollTop
// 1.保持显示区域一直在屏幕上
this.offsetY = scrollTop - (scrollTop % this.itemH)
// 2.计算卷起多少个,替换更新
let startIndex = Math.floor(scrollTop / this.itemH)
this.nowList = this.list.slice(startIndex, startIndex + this.pageNum)
}
}
})
</script>