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

一、整体思路

1.思路来源

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

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

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

2.思路确认

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

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

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

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

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

如果皮皮怪们将滚动条滚到了大于本来待加载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 的高度。

最后我们来看看效果图

总结

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

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

(0)

相关推荐

  • vue大数据表格卡顿问题的完美解决方案

    前言 vue渲染小数据挺快,大数据vue开始出现卡顿现象,本文讲给大家详细介绍关于vue大数据表格卡顿问题的解决方法 点我在线体验Demo(请用电脑查看) 亲测苹果电脑,chrome浏览器无卡顿现象,其它浏览器并未测试,如遇到卡顿请备注系统和浏览器,方便我后续优化,谢谢 先看一下效果,一共1000 X 100 = 10W个单元格基本感受不到卡顿,而且每个单元格点击可以编辑,支持固定头和固定列 项目源代码地址 Github (本地下载) 解决问题核心点:横向滚动加载,竖向滚动加载 项目背景 笔者最

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

    一.整体思路 1.思路来源 最近工作比较忙好久没写文章了,有一丢丢不知道如何写起了,那就先说说我是为什么要开发本文的组件把.公司有一个定位系统,基本上来说一个定位单位一分钟或者更短就会有一个定位点,这样一天下来就会有很多定位点了,如果表格想要一下子放一天甚至三天的数据,那么数据量将会特别大(可能会到达5万条左右的数据),如果我们显示的列又比较多的话,那么表格的卡顿问题就会很明显了.我们公司web端选择的ui框架是 iview  ,说实话 iview 的其他组件还行,不过表格的话在大量数据面前显得

  • PHP实现实时生成并下载超大数据量的EXCEL文件详解

    前言 最近在工作中接到一个需求,通过选择的时间段导出对应的用户访问日志到excel中, 由于用户量较大,经常会有导出50万加数据的情况.而常用的PHPexcel包需要把所有数据拿到后才能生成excel, 在面对生成超大数据量的excel文件时这显然是会造成内存溢出的,所以考虑使用让PHP边写入输出流边让浏览器下载的形式来完成需求. 我们通过如下的方式写入PHP输出流 $fp = fopen('php://output', 'a'); fputs($fp, 'strings'); .... ...

  • 详解基于vue的服务端渲染框架NUXT

    Nuxt 随着现在vue和react的流行,许多网站都做成了SPA,确实提升了用户体验,但SPA也有两个弱点,就是SEO和首屏渲染速度.为了解决单页应用的痛点,基于vue和react的服务端渲染应运而生.由于公司的框架采用的是vue,所以就简单的研究了一下基于vue的服务端渲染框架--NUXT.在vue的官网有关于服务端渲染的详细介绍,而NUXT集成了利用Vue开发服务端渲染的应用所需要的各种配置,也集成了Vue2.vue-router.vuex.vux-meta(管理页面meta信息的),利用

  • Mint UI 基于 Vue.js 移动端组件库

    官网地址 http://mint-ui.github.io/ Mint UI 包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要.通过它,可以快速构建出风格统一的页面,提升开发效率. 真正意义上的按需加载组件.可以只加载声明过的组件及其样式文件,无需再纠结文件体积过大. 考虑到移动端的性能门槛,Mint UI 采用 CSS3 处理各种动效,避免浏览器进行不必要的重绘和重排,从而使用户获得流畅顺滑的体验. 依托 Vue.js 高效的组件化方案,Mint UI 做到了轻量化.即使全部引

  • asp.net的web页面(aspx)数据量过多时提交失败对策

    asp.net的web页面,数据量过多是提交失败,对策: web.config文件中,增加 复制代码 代码如下: <appSettings> ... <add key="aspnet:MaxHttpCollectionKeys" value="5000" /> ... </appSettings>

  • 基于Vue的移动端图片裁剪组件功能

    最近项目上要做一个车牌识别的功能.本来以为很简单,只需要将图片扔给后台就可以了,但是经测试后识别率只有20-40%.因此产品建议拍摄图片后,可以对图片进行拖拽和缩放,然后裁剪车牌部分上传给后台来提高识别率.刚开始的话还是百度了一下看看有没有现成的组件,但是找来找去都没有找到一个合适的,还好这个功能不是很着急,因此自己周末就在家里研究一下. Demo地址:https://vivialex.github.io/demo/imageClipper/index.html 下载地址:https://git

  • 基于vue实现移动端圆形旋钮插件效果

    最近公司有一个项目的需要做出旋钮的效果,旋钮有十个档,根据手指旋转切换,旋转时接近最近的档会有吸附效果,具体效果如下: html部分代码: <div class="_touch"> <div class="round_box" ref="box" @touchstart="touchStart" @touchmove="touchMove"> <div v-if="a

  • 基于JS实现web端录音与播放功能

    纯js实现web端录音功能,功能并不是特别多,逐步增加中,详细地址:github. getUserMedia在非localhost和127的情况下,需要开启https,由于腾讯云的没备案,demo就不放了,可以自行获取代码并启动测试. 实现方式 实现原理的话,主要是以下三点, 利用webrtc的getUserMedia方法获取设备音频输入,使用audioprocess得到音频流(pcm流,范围-1到1). 转码,利用前端中的ArrayBuffer等二进制操作按采样位数处理流信息. 使用decod

  • vue 实现Web端的定位功能 获取经纬度

    首先我这里的需求呢, 是获取当前用户的经纬度 经过无数次的测试, 先后用了 腾讯/百度地图的api,最后绝对还是高德的js APi 废话不多说, 直接上代码. 首先在 index.html 里面 引入 <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=你申请的key"></script> 然后 去你需要获取的页面 开始写, 我这里

  • vue-image-crop基于Vue的移动端图片裁剪组件示例

    本文介绍了vue-image-crop基于Vue的移动端图片裁剪组件示例,分享给大家,具体如下: 代码地址:https://github.com/jczzq/vue-image-crop vue-image-crop 基于Vue的移动端图片裁剪组件 组件使用URL.createObjectURL()等新特性,使用前注意兼容问题.IE >= 10 注意:该组件适用于 PC 端,不推荐手机端使用. 功能预览 快速开始 安装Node >= 8.9.0(推荐LTS = 8.11.0) # 安装 vue

随机推荐