这几个月接了一个日志收集系统的活,由于这个系统是属于传承的项目,因此我也想在系统上作一些标志性的改动,做为接力棒传递下去。前端
这个日志系统从前端到服务端,都作了不小的改动,好比虚拟列表,electron热更新,sql优化,增长了用户的概念,也就是须要登陆,把数据库升级为Elasticsearch,等等。我以后会把一些我感受比较好玩的东西分享出来。vue
我最早操刀修改的是日志list的展现。这个list就是比较常规的堆节点,页面会随着时间的增长致使DOM节点愈来愈多,这是一个不得不改的问题。ajax
首先,由于同事们对这个列表的长时间的使用已经习惯了,因此最好在体验上不要进行大的修改。为此,我须要把以前大概的功能列举出来:sql
总结完以前的功能以后,我须要再梳理一下个人需求:数据库
梳理完成以后,通过考虑我决定使用虚拟列表来代替现有的长列表,这也是踩坑之路的开始。数组
可能以前你们多多少少会据说过这个概念甚至开发过虚拟列表,我无论。在开始以前我仍是先和你们捋一下概念。(敲黑板!!!)浏览器
咱们知道,在浏览器渲染页面的时候,当DOM节点的数量越多,每一次重绘的时候,对性能的影响也就越大。缓存
假如咱们须要展现一个信息量很大,大约有数十万条数据。遇到这样子的状况,其实如今有许多的方案,咱们最多见的方案就相似PC上的下一页、上一页,可是这个方案在体验上其实并不友好。大部分的用户会比较喜欢不停的向下滚动就能够看到新的内容,可是这个就会遇到一个问题,不停的加载数据,致使页面堆积的节点愈来愈多,所消耗的内存不断增大,最后连滚动都会卡顿。bash
这时候咱们从新分析一下,就会发现其实有不少数据咱们大多数状况下是不须要看见的,若是只考虑咱们能看到数据的话,其实须要渲染的数据量就会很是的少了,很好的提升了渲染的效率,减小由于大量的重绘照成没必要要的影响。微信
这么一梳理一下,答案简直呼之欲出----虚拟列表。
虚拟列表其实没有什么特别神奇的地方,说白了就是一种展现列表的思路,在页面上建立一个容器做为可视区,在这个可视区内展现长列表中的一部分,也就是在可视区渲染列表。
如图中所示,是一个简单的虚拟列表的模型,图中有几个概念须要你们稍微了解一下:
可视区你们能够这么理解,咱们如今有一个<div class="show-box">
,给这个元素加一些样式。
.show-box{
width: 375px;
height: 500px;
margin: 0 auto;
position: relative;
}
复制代码
经过这个样式咱们能够看出这个可视区容器的高度为500px
。
真实列表就是会被渲染出来的列表,这么说可能不太理解,举个栗子:如今须要被渲染出来的列表数量一共有1000
条,可是实际上在页面须要被渲染的列表数量(须要被看到的数据)只须要100
条,这个100
条就是所谓的真实列表。
<div class="list-body-box" @scroll="listScroll"> ----- 真实列表
<div class="list-body"> ------ 载体
</div>
</div>
-------------------------- style --------------------------------
.list-body {
min-height: 10px;
position: absolute;
width: 100%;
}
复制代码
在这里,建议真实列表的长度须要比可视区的高度长一些,有一个滚动条的话,以后能够经过scroll
监听作一些其余的操做。
可能有一个点须要和你们解释一下为何个人<div class="list-body">
是绝对定位。
当你的某一个元素会频繁发生变化的时候,最好将这个模块经过绝对定位的方式,脱离文档流,能够减小重绘带来的影响。
咱们先看一下浏览器的渲染机制
绝对定位或者浮动脱离了正常的文档流,至关于只是在节点上存放了一个token,而后经过这个token去进行映射,因此若是你采用了绝对定位的方法,也只会对这一块元素进行重绘。
以前也说到了,真实列表实际上只是总列表其中很小的一部分,在这以外还有不少列表须要被渲染。所以,你们能够把真实列表理解为一个片断。被渲染的第一个元素的index
就是片断中第一个元素在总列表中的位置,也就是数组中的index
。
举个栗子:个人总列表(数组)的长度为1000
,而须要渲染的列表片断为100—200
,那么这个开始的位置,也就是数组的index
则为99
。
解释同上,最后一个元素的index
是199
。
这里要提一下,个人框架用的是vue,因此虚拟列表的实现也是比较方便的。
<div class="list-body-box">
<div class="list-body">
<div
v-for="(item, idx) in list"
v-if="idx >= startIdx && idx <= endIdx"
:key="idx"
class="list-row">
<div class="col-item col-1">{{item.col_1}}</div>
<div class="col-item col-2">{{item.col_2}}</div>
<div class="col-item col-3">{{item.col_3}}</div>
<div class="col-item col-4">{{item.col_4}}</div>
<div class="col-item col-5">{{item.col_5}}</div>
<div class="col-item col-6">{{item.col_6}}</div>
<div class="col-item col-7">{{item.col_7}}</div>
</div>
</div>
</div>
复制代码
模板上,没有什么太特别的地方,主要就是经过v-if
去控制列表的展现,经过startIdx
和endIdx
的增减,去展现不一样位置的数据,让这两个值递增就能够实现列表滚动。
下边咱们会说一下自动滚动在代码上的实现,主要是经过一个主动的事件去频繁的触发对startIdx
和endIdx
递增或者递减。
let time = null;
...
autoScroll(){
time = setTimeout(()=>{
let listLen = this.list.length - 1;
this.endIdx = listLen;
this.startIdx = (listLen + 1) <= 100 ? 0 : listLen - 100;
this.autoScroll();
},300);
}
复制代码
如上代码所示,我只须要再让一个方法去触发autoScroll()
,这个方法就会在setTimeout
的做用下自调用,startIdx
和endIdx
会不断递增列表就能够自动滚动了,在这里边有一个表达式
this.startIdx = (listLen + 1) <= 100 ? 0 : listLen - 100;
复制代码
这一块的话主要是解决当页面刚打开或者清空列表的时候,实际上列表的长度比较短,是不须要进行滚动的,换句话说,startIndex
须要在列表总长度在到达一个值以前一直为0
。
到这里,简单的虚拟列表就实现了。
咱们使用的是WebSocket来传递数据,数据量很多。所以极可能会出现过于频繁更新数据的状况,数据一更新,页面也会随之改变,这样会对性能照成必定的影响。因此咱们须要对这个频度进行把控。目前的方案是加一个缓冲池。
let socketPool = []; //存储一段时间的数据
let socketTimer;
socketFun( (data) => {
//先制造一个缓存区间,用来作缓存socket的数据
socketPool.push(data);
//每次都把当前的数据进行push到list
if(!socketTimer){
socketTimer = setTimeout(()=>{
this.appendRecord(socketPool);
socketPool.length = 0;
this.scrollToBottom();
socketTimer = null;
},500);
}
});
复制代码
在这里边appendRecord()
是用来处理数据,而且把数据放入list中的方法,而scrollToBottom()
就是为了当数据push到list以后,列表能直接展现最新的数据,也就是让页面滚动到列表的最底部。
缓冲池其实也是提高性能的一个方案,这个方案最核心的地方就是减小页面渲染的次数。你们能够这么理解:每秒钟可能会有10条数据须要被渲染,假如我每次都老老实实的渲染,那么10秒的时间我就要渲染10次,实际上是没有必要的,所以咱们能够考虑每2秒渲染一次,这样10s的时间内个人渲染次数就会减小到5次。你能够理解为性能提高了一倍。
按照以前的需求,当用户点击列表中的其中一条数据的时候,列表是须要中止滚动的。因此我加了一个滚动锁autoScrollLoack
,这个锁的做用就是当我点击到列表中的某一条的时候,执行autoScrollLoack = true
页面就不会滚动了。这个锁的判断会放在this.scrollToBottom()
中,代码你们稍微看看就行。
scrollToBottom(){
if(autoScrollLoack){
return;
}
...do something
},
复制代码
这个autoScrollLoack
在页面中会与一个单选框进行双向绑定,所以用户就能够经过改变单选框的选中状态来控制锁的状态,其实在有了这个锁以后,页面若是由于需求中止滚动了,用户也能有所感知,不至于忽然滚动就中止了,看起来像个bug。
聚焦移动的功能以前需求也说过了,就是选中了一条信息,能够经过上下键将聚焦指向上一个或者下一个,这个其实也比较好实现
<div
v-for="(item, idx) in list"
v-if="idx >= startIdx && idx <= endIdx"
:key="item.id"
:class="{'active':curIdx==idx}"
class="list-row"
@click="showDetail(item.id)">
复制代码
在这里,你们能够看到,active
就是聚焦的时候列表的样式。在逻辑上,把当前选中项的index
赋给curIndex
,前端模板上经过vue对class的绑定来控制样式,判断条件就是curIndex == index
。
聚焦功能已经实现了,那么接下来要实现经过键盘中的上下键,实现移动聚焦的效果。这个功能很简单,咱们彻底能够经过vue提供的监听事件来实现,具体的实现你们能够在官网上搜一下keyup
。
<div class="list-body-box" @scroll="listScroll" @keyup="moveFocus">
...
...
moveFocus(e){
let keyCode = Number(e.keyCode);
switch(keyCode){
case 38:
this.curIdx -= 1;
this.showDetail();
break;
case 40:
this.curIdx += 1;
this.showDetail();
break;
}
},
复制代码
这段代码实现了聚焦的上下挪动。根据需求咱们每一次聚焦的时候须要展现聚焦项对应的日志详情,详情是须要发ajax请求来获取的。问题来了,有一个场景:我想经过键盘把当前的聚焦向下挪动10次,在不停聚焦的过程当中我会触发10次请求,这个其实不必,我在快速移动的过程当中,是不care
详情的,我只须要展现目标详情就好了。综上,咱们须要再加一个防抖。
let detailTimer;
showDetail(id){
if(detailTimer){
clearTimeout(detailTimer);
}
detailTimer = setTimeOut(() => {
$.post('...',{
id:id
}).then((res) => {
do something...
});
},300);
}
复制代码
从上边的逻辑咱们能够看出来,当用户在快速挪动聚焦的时候是不会触发请求的,实际上这个改动很大程度上提高了用户的流畅度。
其实虚拟列表的开发仍是比较简单的,可是实际意义却比较大,在这个过程当中会涉及到很多页面的优化,感兴趣的童鞋能够尝试一下,欢迎你们加我微信交流:zyf348519452,咱们下期见~