基于vue实现web端超大数据量表格的卡顿解决


Posted in Javascript onApril 02, 2019

一、整体思路

1.思路来源

最近工作比较忙好久没写文章了,有一丢丢不知道如何写起了,那就先说说我是为什么要开发本文的组件把。公司有一个定位系统,基本上来说一个定位单位一分钟或者更短就会有一个定位点,这样一天下来就会有很多定位点了,如果表格想要一下子放一天甚至三天的数据,那么数据量将会特别大(可能会到达5万条左右的数据),如果我们显示的列又比较多的话,那么表格的卡顿问题就会很明显了。我们公司web端选择的ui框架是 iview  ,说实话 iview 的其他组件还行,不过表格的话在大量数据面前显得很疲软,反而我以前使用的 easyui 之类的老框架的表格性能和功能上都很好,毕竟它们已经经历了很多优化,表格这个组件的拓展性很大,想要在性能和功能上都做好十分的困难。

easyui 是个与时俱进的框架,有一次我点开它的官网发现它已经出了基于现在热门的 vue、react、angular 的ui组件。于是我这次选择去看看它基于vue的表格,于是我看到了这个组件附上连接http://www.jeasyui.net/demo_vue/681.html 。我发现它通过分页延迟加载的方法解决了大数据量卡断的问题,这是我基本能够理解的,不过看完之后我有一些疑问,首先如果他只渲染了一部分数据,在滚动条滚动的时候再加载数据,那么为什么滚动条为什么一直是那么长。机智的我打开了开发者模式查看了表格部分的html代码

基于vue实现web端超大数据量表格的卡顿解决

一看我明白了,图中的表格底部和表格顶部部分就是滚动条高度一直不变的原因,而中间部分更具滚动条的滚动始终只加载40条数据,这样大数据量的表格卡顿问题就解决了

2.思路确认

那么思路我们基本上可以有了,我们来理一下。

首先我们可以认为这个表格分为 3个部分 [表格顶部:( top )、表格滚动区域:( screen )、表格底部:( bottom )]。

topbottom 部分的问题是高度的计算,基本上可以确定应该再滚动条滚动的时候,得到滚动的位置,然后更具总数据量和 screen 部分的数据量计算出 topbottom 的高度,想到这里我脑海里就出现了几个字( 计算属性 )用在这里应该再合适不过了。

screen 部分的实现心中的初步想法是更具滚动高度计算应该加载的数据。不过如何做到在过度数据的时候更加流畅,心中还有一些些疑惑于是我继续观察了它的html。为了更好的表述,前端达芬奇打开了他的画图软件开始了作画(●'◡'●)

基于vue实现web端超大数据量表格的卡顿解决

首先我们刚刚提到了screen部分始终显示40条数据,所以我们通过滚动事件判断当页面滚动到超过screenbottom部分的底部的时候我们就向下加载20条数据同时删除screentop部分的数据这样用户使用的时候不会出现向下滚动加载然后轻微上移又要加载的情况。看到这里很多人肯定在想如果这个用户是个皮皮怪,拉着滚动条疯狂拖动怎么办,那我们就再来看一张图片(●'◡'●)

基于vue实现web端超大数据量表格的卡顿解决

如果皮皮怪们将滚动条滚到了大于本来待加载20条数据高度的位置,我们就用新的处理方式删除所有的40条数据,根据滚动的位置计算当前位置上下各20条的数据。再这个过程当中可能会出现表格变白一下的过程,不过我觉得应该可以通过遮罩层来处理。

基本上的思路有了,那么我们开始实现它吧 (●'◡'●)。

二、实现过程

(首先我先说一下,我实现的这个表格并不是考虑的那么全面,开发的初衷只是解决卡断这个问题,表格排序多选之类的功能等之后再拓展)

1.表格的结构

表格通过2个table标签组成,第一个是表头第二个是数据内容,方便后期拓展。这里偷懒没有把表头和内部内容和tr再单独成一个组件让代码可读性更好之后还可以再优化。

2.逻辑实现

我们直接说最主要的逻辑部分,首先我们看看props和data部分

props: {
 loadNum: {
  //默认加载行数
  type: [Number, String],
  default() {
  return 20;
  }
 },
 tdHeight: {
  //表格行高
  type: [Number, String],
  default() {
  return 40;
  }
 },
 tableHeight: {
  //表格高度
  type: [Number, String],
  default() {
  return "200";
  }
 },
 tableList: {
  //所有表格数据
  type: Array,
  default() {
  return [];
  }
 },
 columns: {
  //所有表格匹配规则
  type: Array,
  default() {
  return [];
  }
 },
 showHeader: {
  type: Boolean,
  default: true
 }
 },
 data() {
 return {
  isScroll: 17,
  showLoad: false,
  columnsBottom: [], //实际渲染表格规则
  showTableList: [], //实际显示的表格数据
  loadedNum: 0, //实际渲染的数据数量
  dataTotal: 0, //总数据条数
  dataTop: 0, //渲染数据顶部的高度
  scrollTop: 0, //滚动上下的距离
  interval: null, //判断滚动是否停止的定时器
  scrollHeight: 0, //数据滚动的高度
  selectTr: -1 //选择的行
 };
 },

然后我们看看滚动事件应该做一些什么先上代码

//滚动条滚动 
handleScroll(event) {
 let bottomScroll = document.getElementById("bottomDiv");
 let topScroll = document.getElementById("topDiv");
 if (bottomScroll.scrollTop > this.scrollTop) {
  //记录上一次向下滚动的位置  this.scrollTop = bottomScroll.scrollTop;  //向下滚动 
  this.handleScrollBottom();
 } else if (bottomScroll.scrollTop < this.scrollTop) {
  //记录上一次向上滚动的位置  
  this.scrollTop = bottomScroll.scrollTop;
  //向上滚动  
  this.handleScrollTop();
 } else {
  //左右滚动  
  this.handleScrollLeft(topScroll, bottomScroll);
 }
}

首先我们通过 scrollTop 这个变量在每次进入滚动事件的时候记录垂直滚动条的位置,如果这个值 不变 那么这次滚动就是 左右滚动, 如果这个值 变大 看那么就是 向下滚动 ,如果这个值 变小 了那么就是 向上滚动 。左右滚动的时候我们需要做的事情就是让表头随着内容一起移动,这样就可以达到左右移动表头动上下移动表头固定的效果。

//滚动条左右滚动
 handleScrollLeft(topScroll, bottomScroll) {
  //顶部表头跟随底部滚动
  topScroll.scrollTo(bottomScroll.scrollLeft, topScroll.pageYOffset);
 },

如果是向上移动我们就要做我们在思路中提高的事情了先看代码

//滚动条向上滚动
 handleScrollTop() {
  //如果加载的数据小于默认加载的数据量
  if (this.dataTotal > this.loadNum) {
  let computeHeight = this.dataTop; //数据需要处理的时候的高度
  if (
   this.scrollTop < computeHeight &&
   this.scrollTop >= computeHeight - this.loadNum * this.tdHeight
  ) {
   this.showLoad = true;
   //如果滚动高度到达数据显示顶部高度
   if (this.dataTotal > this.loadedNum) {
   //如果数据总数大于已经渲染的数据
   if (this.dataTotal - this.loadedNum >= this.loadNum) {
    //如果数据总数减去已经渲染的数据大于等于loadNum
    this.dataProcessing(
    this.loadNum,
    this.loadedNum - this.loadNum,
    "top"
    );
   } else {
    this.dataProcessing(
    this.dataTotal - this.loadedNum,
    this.dataTotal - this.loadedNum,
    "top"
    );
   }
   }
  } else if (
   this.scrollTop <
   computeHeight - this.loadNum * this.tdHeight
  ) {
   this.showLoad = true;
   let scrollNum = parseInt(this.scrollTop / this.tdHeight); //滚动的位置在第几条数据
   if (scrollNum - this.loadNum >= 0) {
   this.dataProcessing(this.loadNum * 2, scrollNum, "topAll");
   } else {
   this.dataProcessing(scrollNum + this.loadNum, scrollNum, "topAll");
   }
  }
  }
 },
  • 首先我们判断加载的数据是否小于默认加载的数据量,如果时那么就不需要做任何逻辑了,因为已经加载了所有的数据了。
  • 判断滚动高度是不是已经超过了当前screen部分数据的顶部位置并且小于当前screen部分数据的顶部位置减去默认加载数据量的高度,也就是我们之前提到第一种情况,那么大于当前screen部分数据的顶部位置减去默认加载数据量的高度就是第二种情况了。
  • 如果进入2个判断this.showLoad设置为true,将遮罩层打开,避免表格变白影响用户的体验,提示在加载。
  • 第一种情况如果数据顶部小于默认加载数据,我们只加载剩余高度的数据如果大于则加载默认加载的this.loadNum数量的数据
  • 第二种情况也是一样判断只不过判断this.loadNum*2是否大于数据顶部的数据条数,只加载剩余高度的数据或者加载this.loadNum*2数量的数据。

向下滚动其实是一样的思路我们看一下代码

//滚动条向下滚动
 handleScrollBottom() {
  let computeHeight =
  this.dataTop +
  this.loadedNum * this.tdHeight -
  (this.tableHeight - this.tdHeight - 3); //数据需要处理的时候的高度
  if (
  this.scrollTop > computeHeight &&
  this.scrollTop <= computeHeight + this.tdHeight * this.loadNum
  ) {
  this.showLoad = true;
  //如果滚动高度到达数据显示底部高度
  if (this.dataTotal > this.loadedNum) {
   //如果数据总数大于已经渲染的数据
   if (this.dataTotal - this.loadedNum >= this.loadNum) {
   //如果数据总数减去已经渲染的数据大于等于20
   this.dataProcessing(
    this.loadedNum - this.loadNum,
    this.loadNum,
    "bottom"
   );
   } else {
   this.dataProcessing(
    this.dataTotal - this.loadedNum,
    this.dataTotal - this.loadedNum,
    "bottom"
   );
   }
  }
  } else if (
  this.scrollTop >
  computeHeight + this.tdHeight * this.loadNum
  ) {
  this.showLoad = true;
  let scrollNum = parseInt(this.scrollTop / this.tdHeight); //滚动的位置在第几条数据
  if (scrollNum + this.loadNum <= this.dataTotal) {
   this.dataProcessing(scrollNum, this.loadNum * 2, "bottomAll");
  } else {
   this.dataProcessing(
   scrollNum,
   this.dataTotal - scrollNum + this.loadNum,
   "bottomAll"
   );
  }
  }
 },

计算了好了有4种情况,并且计算出了对应需要删除和新增的数据量。我们来看看dataProcessing这个函数做了什么事情。

//上下滚动时数据处理
 dataProcessing(topNum, bottomNum, type) {
  let topPosition = parseInt(this.dataTop / this.tdHeight);
  if (type === "top") {
  this.showTableList.splice(this.loadedNum - bottomNum, bottomNum); //减去底部数据
  for (var i = 1; i <= topNum; i++) {
   //加上顶部数据
   let indexNum = topPosition - i;
   this.tableList[indexNum].index = indexNum + 1;
   this.showTableList.unshift(this.tableList[indexNum]);
  }
  this.loadedNum = this.loadedNum + topNum - bottomNum; //重新计算实际渲染数据条数
  this.dataTop = this.dataTop - topNum * this.tdHeight; //重新计算渲染数据的高度
  document.getElementById("bottomDiv").scrollTop =
   document.getElementById("bottomDiv").scrollTop +
   bottomNum * this.tdHeight;
  this.scrollTop = document.getElementById("bottomDiv").scrollTop;
  } else if (type == "bottom") {
  this.showTableList.splice(0, topNum); //减去顶部数据
  for (var i = 0; i < bottomNum; i++) {
   //加上底部数据
   let indexNum = topPosition + this.loadedNum + i;
   this.tableList[indexNum].index = indexNum + 1;
   this.showTableList.push(this.tableList[indexNum]);
  }
  this.loadedNum = this.loadedNum - topNum + bottomNum; //重新计算实际渲染数据条数
  this.dataTop = this.dataTop + topNum * this.tdHeight; //重新计算渲染数据的高度
  document.getElementById("bottomDiv").scrollTop =
   document.getElementById("bottomDiv").scrollTop -
   topNum * this.tdHeight;
  this.scrollTop = document.getElementById("bottomDiv").scrollTop;
  } else if (type == "bottomAll") {
  this.showTableList = []; //减去顶部数据
  let scrollNum = topNum;
  for (var i = 0; i < bottomNum; i++) {
   //加上底部数据
   let indexNum = scrollNum - this.loadNum + i;
   this.tableList[indexNum].index = indexNum + 1;
   this.showTableList.push(this.tableList[indexNum]);
  }
  this.loadedNum = bottomNum; //重新计算实际渲染数据条数
  this.dataTop = (scrollNum - this.loadNum) * this.tdHeight; //重新计算渲染数据的高度
  this.scrollTop = document.getElementById("bottomDiv").scrollTop;
  } else if (type == "topAll") {
  this.showTableList = []; //减去顶部数据
  let scrollNum = bottomNum;
  for (var i = 0; i < topNum; i++) {
   //加上底部数据
   let indexNum = scrollNum - topNum + this.loadNum + i;
   this.tableList[indexNum].index = indexNum + 1;
   this.showTableList.push(this.tableList[indexNum]);
  }
  this.loadedNum = topNum; //重新计算实际渲染数据条数
  this.dataTop = (scrollNum - topNum + this.loadNum) * this.tdHeight; //重新计算渲染数据的高度
  this.scrollTop = document.getElementById("bottomDiv").scrollTop;
  }
  this.showLoad = false;
 },
  • 首先先删除我们之前计算好的应该删除的数据我们用 splice 方法删除对应的数据,然后通过一个简单的for循环,如果是向上滚动应该将数据加在顶部我们用 unshift 方法,如果是向下滚动我们应该加在底部我们用 push 方法。
  • 处理好数据以后我们还需要重新计算实际渲染数据条数,将loadedNum的值改为现在显示的数据条数
  • 重新计算渲染数据的高度,计算出dataTop现在显示的数据顶部的高度
  • 因为 topbottom 的变化会导致表格scrollTop的值出现变化,这个时候我们就要动态把滚动条移动到正确的位置

最后我们来说说之前考虑的 topbottom ,一开始我们就想好了应该用计算属性去做,事实也说明的确这样,我们看看代码

computed: {
 tableOtherTop() {
  //表格剩余数据顶部高度
  return this.dataTop;
 },
 tableOtherBottom() {
  //表格剩余数据底部高度
  return (
  this.dataTotal * this.tdHeight -
  this.dataTop -
  this.loadedNum * this.tdHeight
  );
 }
 },

这样就能保证 topbottom 高度的变化能够触发表格的变化。

top 的高度应该就是显示数据顶部的高度( dataTop )。

bottom 的高度应该就是数据的总高度-显示的数据的高度( this.loadedNum * this.tdHeight )- top 的高度。

最后我们来看看效果图

基于vue实现web端超大数据量表格的卡顿解决

总结

这个组件的开发最麻烦的地方就是理清楚各种情况,然后写好各种计算保证不出错。开发的过程中我也有一种想要自己开发个简易table组件的想法,无奈感觉个人水平有限,不过我也在很多地方做了伏笔,等以后有时间再来拓展这个组件,加油~~~///(^v^)\\\~~~。

这里附上我的 github 地址 https://github.com/github307896154/ExtraLongTable ,我把项目已经上传上去了,如果喜欢可以给我个start,谢谢(●'◡'●),可能其中还存在很多问题,也希望能够得到各位大佬的指点。

Javascript 相关文章推荐
基于jquery的大众点评,分类导航实现代码
Aug 23 Javascript
JS 实现导航栏悬停效果(续2)
Sep 24 Javascript
js delete 用法(删除对象属性及变量)
Aug 24 Javascript
Javascript中获取对象的原型对象的方法小结
Feb 25 Javascript
如何用js 实现依赖注入的思想,后端框架思想搬到前端来
Aug 03 Javascript
微信小程序 animation API详解及实例代码
Oct 08 Javascript
jstree的简单实例
Dec 01 Javascript
fckeditor部署到weblogic出现xml无法读取及样式不能显示问题的解决方法
Mar 24 Javascript
vue表单绑定实现多选框和下拉列表的实例
Aug 12 Javascript
结合mint-ui移动端下拉加载实践方法总结
Nov 08 Javascript
小程序云开发之用户注册登录
May 18 Javascript
javascript设计模式 ? 观察者模式原理与用法实例分析
Apr 22 Javascript
基于AngularJS拖拽插件ngDraggable.js实现拖拽排序功能
Apr 02 #Javascript
详解vue后台系统登录态管理
Apr 02 #Javascript
微信小程序实现人脸识别登陆的示例代码
Apr 02 #Javascript
Angular7中创建组件/自定义指令/管道的方法实例详解
Apr 02 #Javascript
Node.js中package.json中库的版本号(~和^)
Apr 02 #Javascript
基于Vue插入视频的2种方法小结
Apr 02 #Javascript
vue踩坑记-在项目中安装依赖模块npm install报错
Apr 02 #Javascript
You might like
php登录超时检测功能实例详解
2017/03/21 PHP
Javascript 对象的解释
2008/11/24 Javascript
jQuery DIV弹出效果实现代码
2009/07/03 Javascript
jquery控制页面部分刷新的方法
2015/06/24 Javascript
jQuery右侧选项卡焦点图片轮播特效代码分享
2015/09/05 Javascript
jQuery实现连续动画效果实例分析
2015/10/09 Javascript
快速学习jQuery插件 jquery.validate.js表单验证插件使用方法
2015/12/01 Javascript
javascript实现滑动解锁功能
2017/03/22 Javascript
nodejs密码加密中生成随机数的实例代码
2017/07/17 NodeJs
jQuery获取table表中的td标签(实例讲解)
2017/07/28 jQuery
详解webpack的配置文件entry与output
2017/08/21 Javascript
详解vue 项目白屏解决方案
2018/10/31 Javascript
vue+django实现一对一聊天功能的实例代码
2019/07/17 Javascript
详解vue-cli项目开发/生产环境代理实现跨域请求
2019/07/23 Javascript
javascript(基于jQuery)实现鼠标获取选中的文字示例【测试可用】
2019/10/26 jQuery
[48:56]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 VG vs KG
2018/03/31 DOTA
[01:52]PWL S2开团时刻第四期——DOTA2成语故事
2020/12/03 DOTA
举例介绍Python中的25个隐藏特性
2015/03/30 Python
编写Python脚本抓取网络小说来制作自己的阅读器
2015/08/20 Python
深度定制Python的Flask框架开发环境的一些技巧总结
2016/07/12 Python
python 含子图的gif生成时内存溢出的方法
2019/07/07 Python
Django admin model 汉化显示文字的实现方法
2019/08/12 Python
python 使用pygame工具包实现贪吃蛇游戏(多彩版)
2019/10/30 Python
Python unittest单元测试框架及断言方法
2020/04/15 Python
python开发一个解析protobuf文件的简单编译器
2020/11/17 Python
css3实现背景图片拉伸效果像桌面壁纸一样
2013/08/19 HTML / CSS
使用HTML5 Canvas绘制圆角矩形及相关的一些应用举例
2016/03/22 HTML / CSS
html5 canvas简单封装一个echarts实现不了的饼图
2018/06/12 HTML / CSS
英国最大的独立摄影零售商:Park Cameras
2019/11/27 全球购物
Hobbs官方网站:英国奢华女性时尚服装
2020/02/22 全球购物
大学生村官任职感言
2014/01/09 职场文书
市场开发计划书
2014/05/07 职场文书
县长群众路线对照检查材料思想汇报
2014/10/02 职场文书
2014院党委领导班子及其成员群众路线对照检查材料思想汇报
2014/10/04 职场文书
2016学习雷锋精神活动倡议书
2015/04/27 职场文书
德劲DE1108畅想
2021/04/22 无线电