是
绝对定位。
当你的某一个元素会频繁发生变化的时候,最好将这个模块通过绝对定位的方式,脱离文档流,可以减少重绘带来的影响。
我们先看一下浏览器的渲染机制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。
绝对定位或者浮动脱离了正常的文档流,相当于只是在节点上存放了一个token,然后通过这个token去进行映射,所以如果你采用了绝对定位的方法,也只会对这一块元素进行重绘。
startIndex
之前也说到了,真实列表实际上只是总列表其中很小的一部分,在这之外还有很多列表需要被渲染。因此,大家可以把真实列表理解为一个片段。被渲染的第一个元素的index
就是片段中第一个元素在总列表中的位置,也就是数组中的index
。
举个栗子:我的总列表(数组)的长度为1000
,而需要渲染的列表片段为100—200
,那么这个开始的位置,也就是数组的index
则为99
。
edIndex
解释同上,最后一个元素的index
是199
。
虚拟列表的实现
这里要提一下,我的框架用的是vue,所以虚拟列表的实现也是比较方便的。
&#61; startIdx && idx <&#61; endIdx" :key&#61;"idx"class&#61;"list-row">
{{item.col_1}}
{{item.col_2}}
{{item.col_3}}
{{item.col_4}}
{{item.col_5}}
{{item.col_6}}
{{item.col_7}}
模板上&#xff0c;没有什么太特别的地方&#xff0c;主要就是通过v-if
去控制列表的展示&#xff0c;通过startIdx
和endIdx
的增减&#xff0c;去展示不同位置的数据&#xff0c;让这两个值递增就可以实现列表滚动。
下边我们会说一下自动滚动在代码上的实现&#xff0c;主要是通过一个主动的事件去频繁的触发对startIdx
和endIdx
递增或者递减。
let time &#61; null;
...
autoScroll(){time &#61; setTimeout(()&#61;>{let listLen &#61; this.list.length - 1;this.endIdx &#61; listLen;this.startIdx &#61; (listLen &#43; 1) <&#61; 100 ? 0 : listLen - 100;this.autoScroll();},300);
}
如上代码所示&#xff0c;我只需要再让一个方法去触发autoScroll()
&#xff0c;这个方法就会在setTimeout
的作用下自调用&#xff0c;startIdx
和endIdx
会不断递增列表就可以自动滚动了,在这里边有一个表达式
this.startIdx &#61; (listLen &#43; 1) <&#61; 100 ? 0 : listLen - 100;
这一块的话主要是解决当页面刚打开或者清空列表的时候&#xff0c;实际上列表的长度比较短&#xff0c;是不需要进行滚动的&#xff0c;换句话说&#xff0c;startIndex
需要在列表总长度在到达一个值之前一直为0
。
到这里&#xff0c;简单的虚拟列表就实现了。
WebSocket缓冲池
我们使用的是WebSocket来传递数据&#xff0c;数据量不少。因此很可能会出现过于频繁更新数据的情况&#xff0c;数据一更新&#xff0c;页面也会随之改变&#xff0c;这样会对性能照成一定的影响。所以我们需要对这个频度进行把控。目前的方案是加一个缓冲池。
这缓冲池的思路大概是这样的&#xff0c;WebSocket传递数据的时候&#xff0c;我们把这段时间的数据先存在一个数组中&#xff0c;然后每隔一段时间&#xff0c;比如500ms&#xff0c;再把数据push到完整的列表中&#xff0c;这个方案可能就会涉及到节流。
let socketPool &#61; []; //存储一段时间的数据
let socketTimer;
socketFun( (data) &#61;> {//先制造一个缓存区间&#xff0c;用来做缓存socket的数据socketPool.push(data);//每次都把当前的数据进行push到listif(!socketTimer){socketTimer &#61; setTimeout(()&#61;>{this.appendRecord(socketPool);socketPool.length &#61; 0;this.scrollToBottom();socketTimer &#61; null;},500);}
});
在这里边appendRecord()
是用来处理数据&#xff0c;并且把数据放入list中的方法&#xff0c;而scrollToBottom()
就是为了当数据push到list之后&#xff0c;列表能直接展示最新的数据&#xff0c;也就是让页面滚动到列表的最底部。
缓冲池其实也是提升性能的一个方案&#xff0c;这个方案最核心的地方就是减少页面渲染的次数。大家可以这么理解&#xff1a;每秒钟可能会有10条数据需要被渲染&#xff0c;假如我每次都老老实实的渲染&#xff0c;那么10秒的时间我就要渲染10次&#xff0c;其实是没有必要的&#xff0c;因此我们可以考虑每2秒渲染一次&#xff0c;这样10s的时间内我的渲染次数就会减少到5次。你可以理解为性能提升了一倍。
列表锁定
按照之前的需求&#xff0c;当用户点击列表中的其中一条数据的时候&#xff0c;列表是需要停止滚动的。所以我加了一个滚动锁autoScrollLoack
&#xff0c;这个锁的作用就是当我点击到列表中的某一条的时候&#xff0c;执行autoScrollLoack &#61; true
页面就不会滚动了。这个锁的判断会放在this.scrollToBottom()
中&#xff0c;代码大家稍微看看就行。
scrollToBottom(){if(autoScrollLoack){return;}...do something
},
这个autoScrollLoack
在页面中会与一个单选框进行双向绑定&#xff0c;因此用户就可以通过改变单选框的选中状态来控制锁的状态&#xff0c;其实在有了这个锁之后&#xff0c;页面如果因为需求停止滚动了&#xff0c;用户也能有所感知&#xff0c;不至于突然滚动就停止了&#xff0c;看起来像个bug。
聚焦移动
聚焦移动的功能之前需求也说过了&#xff0c;就是选中了一条信息&#xff0c;可以通过上下键将聚焦指向上一个或者下一个&#xff0c;这个其实也比较好实现
&#61; startIdx && idx <&#61; endIdx" :key&#61;"item.id":class&#61;"{&#39;active&#39;:curIdx&#61;&#61;idx}"class&#61;"list-row"&#64;click&#61;"showDetail(item.id)">
在这里&#xff0c;大家可以看到&#xff0c;active
就是聚焦的时候列表的样式。在逻辑上&#xff0c;把当前选中项的index
赋给curIndex
&#xff0c;前端模板上通过vue对class的绑定来控制样式&#xff0c;判断条件就是curIndex &#61;&#61; index
。
聚焦功能实现了&#xff0c;接下来要实现通过键盘中的上下键&#xff0c;实现移动聚焦的效果。这个功能很简单&#xff0c;我们完全可以通过vue提供的监听事件来实现&#xff0c;具体的实现大家可以在官网上搜一下keyup
。
...
...
moveFocus(e){let keyCode &#61; Number(e.keyCode);switch(keyCode){case 38:this.curIdx -&#61; 1;this.showDetail();break;case 40:this.curIdx &#43;&#61; 1;this.showDetail();break;}
},
这段代码实现了聚焦的上下挪动。根据需求我们每一次聚焦的时候需要展示聚焦项对应的日志详情&#xff0c;详情是需要发ajax请求来获取的。问题来了&#xff0c;有一个场景&#xff1a;我想通过键盘把当前的聚焦向下挪动10次&#xff0c;在不停聚焦的过程中我会触发10次请求&#xff0c;这个其实没必要&#xff0c;我在快速移动的过程中&#xff0c;是不care
详情的&#xff0c;我只需要展示目标详情就行了。综上&#xff0c;我们需要再加一个节流。
let detailTimer;
showDetail(id){if(detailTimer){clearTimeout(detailTimer);}detailTimer &#61; setTimeOut(() &#61;> {$.post(&#39;...&#39;,{id:id}).then((res) &#61;> {do something... });},300);
}
从上边的逻辑我们可以看出来&#xff0c;当用户在快速挪动聚焦的时候是不会触发请求的&#xff0c;实际上这个改动很大程度上提升了用户的流畅度。
总结
其实虚拟列表的开发还是比较简单的&#xff0c;但是实际意义却比较大&#xff0c;感兴趣的童鞋可以尝试一下。我们下期见~