热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

vue列表长度限定_日志系统之虚拟列表

背景这几个月接了一个日志收集系统的活,因为这个列表是属于传承的项目,所以我也想在系统上做一些比标志性的改动,作为接力棒传递下去。这个日志系

背景

这几个月接了一个日志收集系统的活,因为这个列表是属于传承的项目,所以我也想在系统上做一些比标志性的改动,作为接力棒传递下去。

886f3686e4a6318a2e8558ff6ee7db17.png

这个日志系统从前端到服务端,都做了不小的改动,比如虚拟列表,electron热更新,sql优化,增加了用户的概念,也就是需要登录,把数据库升级为Elasticsearch,等等。我之后会把一些我感觉比较好玩的东西分享出来。

我最先操刀修改的是日志list的展示。这个list就是比较常规的堆节点,页面会随着时间的增加导致DOM节点越来越多,这是一个不得不改的问题。

分析需求

首先,因为同事们对这个列表的长时间的使用已经习惯了,所以最好在体验上不要进行大的修改。为此,我需要把之前大概的功能列举出来:

  • 每次列表有新的消息传入的时候,都要能看到最新的那条数据
  • 当用户点击列表中的其中一条数据的时候,列表需要停止更新,也就是停止滚动
  • 当列表处于锁定状态,滚动条滚动到最底部的时候,列表恢复自动滚动
  • 当列表中有被选中状态的数据时,可以通过上下左右键来让聚焦移动

总结完之前的功能之后,我需要再梳理一下我的需求:

  • 列表随着时间会越来越长,需要控制展示的节点数量
  • 列表长度随着WebSocket的通信而增加,数据更新频度过快,需要有缓冲池
  • 增加一个列表锁定的提示,可以手动解开列表的锁定
  • 移动聚焦的时候会随即展示日志详情,因为移动速度过快,所以需要增加节流

梳理完成之后,经过考虑我决定使用虚拟列表来代替现有的长列表,这也是踩坑之路的开始。

bc5f5bcd12f3255ae88799b292305055.png

开始开发

长列表转虚拟列表

可能之前大家多多少少会听说过甚至处理过虚拟列表这个概念,但是在开始之前我还是先和大家捋一下概念。(敲黑板!!!)

6442286672c4c2c497b12b0606eef828.gif

为什么需要虚拟列表

我们知道,在浏览器渲染页面的时候,当DOM节点的数量越多,每一次重绘的时候,对性能的影响也就越大。

假如我们需要展示一个信息量很大,大约有数十万条数据。遇到这样子的情况,其实现在有许多的方案,我们最常见的方案就类似PC上的下一页、上一页,但是这个方案在体验上其实并不友好。大部分的用户会比较喜欢不停的向下滚动就可以看到新的内容,但是这个就会遇到一个问题,不停的加载数据,导致页面堆积的节点越来越多,所消耗的内存不断增大,最后连滚动都会卡顿。

这时候我们重新分析一下,就会发现其实有很多数据我们大多数情况下是不需要看见的,如果只考虑我们能看到数据的话,其实需要渲染的数据量就会非常的少了,很好的提高了渲染的效率,减少因为大量的重绘照成不必要的影响。

这么一梳理一下答案简直呼之欲出----虚拟列表。

b7df9858aa5ee597749b66905ce31097.png

什么是虚拟列表

虚拟列表其实没有什么特别神奇的地方,说白了就是一种展示列表的思路,在页面上创建一个容器作为可视区,在这个可视区内展示长列表中的一部分,也就是在可视区渲染列表。

88dcd91cbb2037c58617d12cfc1b3e38.png

如图中所示,是一个简单的虚拟列表的模型,图中有几个概念需要大家稍微了解一下:

  • 可视区。
  • 真实列表。
  • startIndex。
  • endIndex。

可视区

可视区大家可以这么理解,我们现在有一个

,给这个元素加一些样式。

.show-box{width: 375px;height: 500px;margin: 0 auto;position: relative;
}

通过这个样式我们可以看出这个可视区容器的高度为500px

真实列表

真实列表就是会被渲染出来的列表,这么说可能不太理解,举个栗子:现在需要被渲染出来的列表数量一共有1000条,但是实际上在页面需要被渲染的列表数量(需要被看到的数据)只需要100条,这个100条就是所谓的真实列表。

----- 真实列表
------ 载体


-------------------------- style --------------------------------
.list-body {min-height: 10px;position: absolute;width: 100%;
}

在这里,建议真实列表的长度需要比可视区的高度长一些,有一个滚动条的话,之后可以通过scroll监听做一些其他的操作。

可能有一个点需要和大家解释一下为什么我的

绝对定位

当你的某一个元素会频繁发生变化的时候,最好将这个模块通过绝对定位的方式,脱离文档流,可以减少重绘带来的影响。

我们先看一下浏览器的渲染机制

509f6190751ca2c2b3fe259fe4329a94.png
  • 解析HTML,生成DOM树,解析CSS,生成CSSOM树
  • 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  • Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
  • Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  • Display:将像素发送给GPU,展示在页面上。

绝对定位或者浮动脱离了正常的文档流,相当于只是在节点上存放了一个token,然后通过这个token去进行映射,所以如果你采用了绝对定位的方法,也只会对这一块元素进行重绘。

93deb2eedcbb856bc55796bb8d49c1be.gif

startIndex

之前也说到了,真实列表实际上只是总列表其中很小的一部分,在这之外还有很多列表需要被渲染。因此,大家可以把真实列表理解为一个片段。被渲染的第一个元素的index就是片段中第一个元素在总列表中的位置,也就是数组中的index

举个栗子:我的总列表(数组)的长度为1000,而需要渲染的列表片段为100—200,那么这个开始的位置,也就是数组的index则为99

edIndex

解释同上,最后一个元素的index199

89fe38f0fd972ba6050901a03001649f.png

虚拟列表的实现

这里要提一下,我的框架用的是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;通过startIdxendIdx的增减&#xff0c;去展示不同位置的数据&#xff0c;让这两个值递增就可以实现列表滚动

下边我们会说一下自动滚动在代码上的实现&#xff0c;主要是通过一个主动的事件去频繁的触发对startIdxendIdx递增或者递减。

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;startIdxendIdx会不断递增列表就可以自动滚动了,在这里边有一个表达式

this.startIdx &#61; (listLen &#43; 1) <&#61; 100 ? 0 : listLen - 100;

这一块的话主要是解决当页面刚打开或者清空列表的时候&#xff0c;实际上列表的长度比较短&#xff0c;是不需要进行滚动的&#xff0c;换句话说&#xff0c;startIndex需要在列表总长度在到达一个值之前一直为0

到这里&#xff0c;简单的虚拟列表就实现了。

0613a6e4899163760f71e8cfe19e6a74.gif

WebSocket缓冲池

我们使用的是WebSocket来传递数据&#xff0c;数据量不少。因此很可能会出现过于频繁更新数据的情况&#xff0c;数据一更新&#xff0c;页面也会随之改变&#xff0c;这样会对性能照成一定的影响。所以我们需要对这个频度进行把控。目前的方案是加一个缓冲池。

86c3ab908a78d1356dee7b6aee4e2280.png

这缓冲池的思路大概是这样的&#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次。你可以理解为性能提升了一倍

c9c53d71feb62eef80361874bec73162.gif

列表锁定

按照之前的需求&#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

d7152927cb4d4de6cacc8a74d82088a8.png


...
...
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;感兴趣的童鞋可以尝试一下。我们下期见~

f188fc55698c31e17030fa120fdae080.gif



推荐阅读
  • Tkinter Frame容器grid布局并使用Scrollbar滚动原理
    本文介绍了如何使用Tkinter实现Frame容器的grid布局,并通过Scrollbar实现滚动效果。通过将Canvas作为父容器,使用滚动Canvas来滚动Frame,实现了在Frame中添加多个按钮,并通过Scrollbar进行滚动。同时,还介绍了更新Frame大小和绑定滚动按钮的方法,以及配置Scrollbar的相关参数。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 第五章:集合01
    第三章:集合01一:集合的框架结构图1.集合和数组的区别:2.Collection集合的方法:publicclassCol ... [详细]
  • DOM事件大全
    1.事件:js与html的交互就是通过事件的,观察者模式2.事件流:从页面中接收事件的顺序IE::事件冒泡流,事件冒泡,事件从最具体的元素接收,然后逐级向上传播,主流浏览器都支持N ... [详细]
  • 数据结构系列1 数组和链表
    数组,链表,l ... [详细]
  • 本文详细介绍了PHP中与URL处理相关的三个函数:http_build_query、parse_str和查询字符串的解析。通过示例和语法说明,讲解了这些函数的使用方法和作用,帮助读者更好地理解和应用。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有