首先给出定义,什么是虚拟列表。
虚拟列表在开发者眼中并不能是一个真正的列表,它可以看作是可视区域中的几条数据,并且可以监听到用户的滚动事件来动态渲染可视区域的显示数据。
但是这个可滚动的可视区域在用户眼中,就是一个列表。
如果有一个长达20w的数据需要渲染。可能由于业务需求或者是被万恶的产品经理压迫导致列表还不能做分页显示。此时我写了个小demo来看看页面的表现
<div id='app'>
<div class="contact-content" id='contactContent'>
<div class='contact-item' v-for="(item, index) in contactList" :key='index'>联系人列表===========> {{item.index}}
div>
div>
div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
script>
<script defer>
const vm = new Vue({
el: '#app',
data: function () {
return {
contactList: [],
reallyShowContactList:[],
wheelHeight:0
}
},
created() {
this.contactList = Array(30000).fill({}).map((item, index) => ({ index }));
},
watch: {
},
methods: {
}
})
script>
一个很简单的demo,这里不做过多解释。此时看看页面表现。
很明显的可以看出页面有肉眼可见的延迟。这仅仅是三十万条简单的数据。实际业务可能更加复杂,数据量也更多。此时用户体验就会更差。
如何实现一个虚拟列表。首先我们得知道,为什么长列表会影响性能。
以30w的数据列表为栗子。虽然数量量有30w。但是展示在用户眼前的可能只有十几条。但是浏览器会把剩下的用户看不见的20几万条都一次性的渲染出来。造成了不必要的性能开销。所以我们针对这点可以做出性能优化。
首先我们对实际渲染的列表做如下修改
<div id='app'>
<div class="contact-content" id='contactContent' >
<div class='contact-item' v-for="(item, index) in reallyShowContactList" :key='index'>联系人列表===========> {{item.index}}
div>
div>
div>
这里看到我把实际渲染的数据列表contactList换成了reallyShowContactList,我们应该如何构造这个reallyShowContactList呢?
const vm = new Vue({
el: '#app',
data: function () {
return {
contactList: [],
reallyShowContactList:[],
wheelHeight:0
}
},
created() {
this.contactList = Array(30000).fill({}).map((item, index) => ({ index }));
this.initShowContactsList();
},
watch: {
},
methods: {
initShowContactsList() {
// 获取列表容器的高度
const contentBoxHeight = $('#contactContent').height();
// 计算出一页展示的数量
const showCount = Math.ceil(contentBoxHeight/30);
this.reallyShowContactList = this.contactList.slice(0, showCount);
}
}
})
实际上,计算出实际应该展示的数据条数是很简单的。如果用户看到的区域高度只有200px。一条数据的长度是20px,那么应该展示的不就是200/20条数据吗。如果不能整除。可以看个人喜好来决定向上取整还是向下取整。
好了,知道实际渲染的数据条数应该如何渲染之后,计算出reallyShowContactList的步骤就很清晰了。我们假设在created里发送了ajax请求拿到了总数据量contactList,之后调用initShowContactsList方法计算出reallyShowContactList。方法中const contentBoxHeight = $('#contactContent').height();
拿到了视觉区域的高度。其实在真实项目中这个高度肯定是开发者自己编写的。不需要通过js获取就应该知道的。这里为了展示其原理还是写一下。const showCount = Math.ceil(contentBoxHeight/30);
计算出应该展示的条数。那么初始列表就应该等于原始列表的前showCount
条数据。this.reallyShowContactList = this.contactList.slice(0, showCount);
此时来看看页面表现
到了此处仅仅只是完成了第一步,毕竟这还只是完成了初始列表的展示。接下来才是虚拟列表最重要的一步。手动实现滚动效果加粗样式。
首先我们在列表的可使区域上绑定页面滚动事件
<div id='app'>
<div class="contact-content" id='contactContent' @wheel='scrollHandler'>
<div class='contact-item' v-for="(item, index) in reallyShowContactList" :key='index'>联系人列表===========> {{item.index}}
div>
div>
div>
vue methods中编写方法如下
scrollHandler(e){
e.preventDefault();
console.log(e.deltaY);
}
页面滚动的鼠标事件event中有个属性e.deltaY可以表示出用户滚动的方向与力度。正数为向下滑动,负数为向上滑动。且滚动力度越大,数值越大。页面表现如下。
掌握了这个属性的使用方法。如何渲染当前列表仿佛已经有了思路。
首先,声明一个初始值为0的变量wheelHeight
。代表着页面滚动的距离。然后通过页面滚动的距离计算出被隐藏掉的列表项数。start= wheelHeight/单项高度
。从而得出需要展示的列表项目等于contactList.slice(start, start + showCount)
showcount上文已经提出,为一页展示的数据。逻辑已经捋清之后马上开干
scrollHandler(e){
e.preventDefault();
// 获取列表容器的高度
const contentBoxHeight = $('#contactContent').height();
// 计算出一页展示的数量
const showCount = Math.ceil(contentBoxHeight/30);
// 滚动高度
this.wheelHeight += e.deltaY;
if (this.wheelHeight < 0 || !this.reallyShowContactList.length){
this.wheelHeight = 0;
return;
}
let start = Math.floor(this.wheelHeight/30);
start = start < 0 ? 0 : start;
this.reallyShowContactList = this.contactList.slice(start, start + showCount);
}
至此 一个初始化极快的虚拟列表大功告成
完整代码供各位看官研究!
<div id='app'>
<div class="contact-content" id='contactContent' style="height: 300px;" @wheel='scrollHandler'>
<div class='contact-item' v-for="(item, index) in reallyShowContactList" :key='index'>联系人列表===========>
{{item.index}}
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script defer>
const vm = new Vue({
el: '#app',
data: function () {
return {
contactList: [],
reallyShowContactList: [],
wheelHeight: 0
}
},
created() {
this.contactList = Array(30000).fill({}).map((item, index) => ({ index }));
this.initShowContactsList();
},
watch: {
},
methods: {
initShowContactsList() {
// 获取列表容器的高度
const contentBoxHeight = $('#contactContent').height();
// 计算出一页展示的数量
const showCount = Math.ceil(contentBoxHeight / 30);
debugger;
this.reallyShowContactList = this.contactList.slice(0, showCount);
},
scrollHandler(e) {
e.preventDefault();
// 获取列表容器的高度
const contentBoxHeight = $('#contactContent').height();
// 计算出一页展示的数量
const showCount = Math.ceil(contentBoxHeight / 30);
// 滚动高度
this.wheelHeight += e.deltaY;
if (this.wheelHeight < 0 || !this.reallyShowContactList.length) {
this.wheelHeight = 0;
return;
}
let start = Math.floor(this.wheelHeight / 30);
start = start < 0 ? 0 : start;
this.reallyShowContactList = this.contactList.slice(start, start + showCount);
}
}
})
</script>
手动码字不易,所以各位看官慷慨给个赞。希望你们不要不识抬举。