为什么要使用虚拟列表?
当数据量很大的时候,像有10万条数据的时候,我们一次性获得所有数据是不可能的,当然我们也可以使用分页的效果,但是这样用户的体验就不好了,用户喜欢往下滑动获得数据展示。当时当我们根据滑动获取数据的时候就会遇到另一个问题,不停的加载数据,导致页面堆积的节点越来越多,内存不断的增加,最后连滚动都出现了卡顿。所以虚拟列表蕴蓄而生
什么是进程?
进程是系统进行资源分配和调度的一个独立单位,一个进程内包含多个线程。
什么是虚拟列表?
虚拟列表也没有什么特别的地方,就是在页面上创建一个容器作为可视区,在这个可视区内展示长列表中的一部分,也就是在可视区域内渲染列表。
一个简单的可视区域模型,大概有五点需要说一下:
- 可视区域大小
- 真实列表大小
- startIndex
- endIndex
可视区域
就是这个盒子的高度
真实列表
真实列表就是被渲染出来的列表,举个例子:现在总数据有1000个,当实际上页面需要被渲染的列表数量只有100个*(就是可视区域内的数量),这 100 个 数据 就是真实列表。
<div @scroll="listScroll">
<div class="list-body">
</div>
</div>
// style
.list-body{
position:absolute;
}
这里建议真实列表的长度需要比可视区域的高度长一点,这样有一个滚动条的话,之后可以通过scroll监听操作,这里说一下为什么用 绝对定位。
当一个元素频繁发生改变的时候,最好将这个模块通过绝对定位的方式,脱离文档流,这样可以减少重绘带来的影响。
浏览器渲染机制
- 解析html,生成DOM树,解析css,生成cssDOM树
- DOM树和CSSDOM树结合,生成渲染树(render tree)
- 回流:根据生成的渲染树,进行回流,得到节点的几何信息(位置,大小)
- 重绘:根据渲染树以及回流得到的几何信息,得到节点的绝对像素。
- 将像素发送到GPU,展示在页面上。
脱离文档流之后,元素发生改变只会对这一部分进行重绘。
startIndex
被渲染的第一个元素的index就是片段中的第一个元素在总列表的位置,也就是数组中的index。
例如:总列表长度 1000 , 渲染片段为 100 - 200 , index就是 99
endindex
endindex就是 199 的位置。
虚拟列表的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.viewport {
overflow-y: scroll;
position: relative;
}
.scroll-list {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<ul id="app">
<!-- 能滚动的盒子 -->
<div class="viewport" ref="viewport" @scroll="handlScroll">
<!-- 滚动条 -->
<div class="scroll-bar" ref="scrollbar"></div>
<!-- 列表位置 -->
<div class="scroll-list" :style="{transform:`translate3d(0,${offset}px,0)`}">
<div v-for="item in visibleData" ref="items" :key="item.id" :vid="item.id">
<!-- 通过插槽传出去 -->
<slot :item="item"></slot>
</div>
</div>
</div>
</ul>
<script src="../../js/vue.js"></script>
<script>
new Vue({
el: '#app',
data(){
return {
list: [],//超长的显示数据
itemHeight: 30,//每一列的高度
showNum: 10,//显示几条数据
start: 0,//滚动过程显示的开始索引
end: 10,//滚动过程显示的结束索引
}
},
computed: {
//显示的数组,用计算属性计算
visibleData() {
let start = this.start - this.prevCount;
let end = this.end + this.nextCount;
return this.items.slice(start, end);
},
//前面预留几个 为了放置空白现象
prevCount() {
return Math.min(this.start, this.showNum);
},
//后面预留几个
nextCount() {
return Math.min(this.showNum, this.items.length - this.end);
},
},
mounted(){
//构造一个超长列表
for (let i = 0; i < 1000000; i++) {
this.list.push('列表' + i)
}
//当前的一项高度*显示的项目高度
this.$refs.viewport.style.height = this.size * this.remain + "px";
//当前总长度*当前的项目高度
this.$refs.scrollbar.style.height = this.items.length * this.size + "px";
},
methods: {
scrollListener(){
//获取滚动高度
let scrollTop = this.$refs.listWrap.scrollTop;
//开始的数组索引
this.start = Math.floor(scrollTop / this.itemHeight);
//结束索引
this.end = this.start + this.showNum;
//绝对定位对相对定位的偏移量
this.$refs.list.style.top = this.start * this.itemHeight + 'px';
}
}
})
</script>
</body>
</html>
看这个吧
手动代码分页
var dqPage = $("#dqPage").text();//得到当前页数
dqPage = parseInt(dqPage);//得到的文本转成int
var pageCount = $("#pageCount").text();//得到总页数
pageCount = parseInt(pageCount);
var i = 1;
i = parseInt(i);
var item="";
var href = "这里是请求地址";
if (pageCount <= 5 ) {//总页数小于五页,则加载所有页
for (i; i <= pageCount; i++) {
if (i == dqPage) {
item += "<span class='disabled'>"+i+"</span>";
}else{
item += "<a href='"+href+i+"' >"+i+"</a>";
}
};
$('#pageBtn').append(item);
return;
}else if (pageCount > 5) {//总页数大于五页,则加载五页
if (dqPage < 5) {//当前页小于5,加载1-5页
for (i; i <= 5; i++) {
if (i == dqPage) {
item += "<span class='disabled'>"+i+"</span>";
}else{
item += "<a href='"+href+i+"' >"+i+"</a>";
}
};
if (dqPage <= pageCount-2) {//最后一页追加“...”代表省略的页
item += "<span> . . . </span>";
}
$('#pageBtn').append(item);
return;
}else if (dqPage >= 5) {//当前页大于5页
for (i; i <= 2; i++) {//1,2页码始终显示
item += "<a href='"+href+i+"' >"+i+"</a>";
}
item += "<span> . . . </span>";//2页码后面用...代替部分未显示的页码
if (dqPage+1 == pageCount) {//当前页+1等于总页码
for(i = dqPage-1; i <= pageCount; i++){//“...”后面跟三个页码当前页居中显示
if (i == dqPage) {
item += "<span class='disabled'>"+i+"</span>";
}else{
item += "<a href='"+href+i+"' >"+i+"</a>";
}
}
}else if (dqPage == pageCount) {//当前页数等于总页数则是最后一页页码显示在最后
for(i = dqPage-2; i <= pageCount; i++){//...后面跟三个页码当前页居中显示
if (i == dqPage) {
item += "<span class='disabled'>"+i+"</span>";
}else{
item += "<a href='"+href+i+"' >"+i+"</a>";
}
}
}else{//当前页小于总页数,则最后一页后面跟...
for(i = dqPage-1; i <= dqPage+1; i++){//dqPage+1页后面...
if (i == dqPage) {
item += "<span class='disabled'>"+i+"</span>";
}else{
item += "<a href='"+href+i+"' >"+i+"</a>";
}
}
item += "<span> . . . </span>";
}
$('#pageBtn').append(item);
return;
}
}