美文网首页
表格虚拟列表渲染的实现

表格虚拟列表渲染的实现

作者: jeneen1129 | 来源:发表于2022-11-25 14:56 被阅读0次

参考链接如下:

https://blog.csdn.net/ZYS10000/article/details/122294309
https://blog.csdn.net/qq_44993023/article/details/125680903

1、使用场景/背景:

表格设置分页或者不设置分页,但是当前页数会显示到几百至上千条时,会造成 dom 渲染的负担,页面以及鼠标卡顿,导致用户体验不好,此种情况,就可以使用虚拟列表渲染来解决一下子渲染 [行数 * 列数]个控件的渲染负担问题。

2、实现思路:

一、虚拟列表是什么?

虚拟列表就是只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而减轻 dom 操作的负担。

image

二、实现思路

1)用一个 container 来撑起所有行的高度,为总的不可见区域(模拟实际滚动区域),利用其对 scroll 事件的监听,通过 scrollTop 来计算渲染数组的起始索引startIndex

2)用一个 container 来撑起当前渲染行的数据,为当前可视区域,该高度应该提前设定或者为固定值(最大高度应该是屏幕的最大可视区域),通过 【该高度 / 行的高度】 从而确定能够显示的行数showNum

3)通过对 startIndexshowNum 的配置可以对需要渲染的数据进行选择性渲染。

3、代码实现:

// 代码可能没法运行,理解思路最好。
在 html (freemarker 模板)中添加相应的 可视区域div、不可见区域div 和 scroll 事件:

<!-- 存放头部-->
<table id="virtual_header" style="table-layout: fixed; width: 100%">
  <colgroup>
    <col style="width: 50px; min-width: 50px" />
  </colgroup>
  <thead>
    <tr>
      <th
        data-index="0"
        @mousemove="handleMouseMove"
        @mouseout="handleMouseOut"
        @mousedown="handleMouseDown"
      >
        序号
      </th>
    </tr>
  </thead>
  <div class="el-table__column-resize-proxy" style="display: none"></div>
</table>

<!-- 存放身体-->
<!-- 可视区域 container -->
<div
  class="list_view"
  @scroll="handleScroll"
  id="list_view"
  :style="{ height: '100vh', width: isScoll() ?  'calc(' + scrollAreaWidth() + ' + 8px)' : scrollAreaWidth() }"
>
  <!-- 不可见区域 container -->
  <div
    v-if="useVirtualRender"
    class="scroll_area"
    :style="{ height: scrollAreaHeight() }"
  ></div>
  <div ref="content_data" class="list_view_content">
    <table style="table-layout: fixed">
      <!-- 虚拟渲染显示,与头部控制相同 -->
      <colgroup>
        <col width="50" />
      </colgroup>
      <tbody>
        <tr v-for="(item, index) in pagingData(true)">
          <td
            align="center"
            style="border-left: 1px solid #ebeef5"
            :data-index="getPagingDataIndex(index)"
          >
            getPagingDataIndex(index)
          </td>
        </tr>
      </tbody>
      <div class="el-table__column-resize-proxy" style="display: none"></div>
    </table>
  </div>
</div>

css 设置高度和定位配置:

.list_view {
  max-height: 100vh;
  overflow: auto;
  position: relative;

  &::-webkit-scrollbar {
    height: 0px;
  }

  .scroll_area {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
  }

  .list_view_content {
    scroll-behavior: smooth; /*滚动平滑属性*/
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
  }
}

vue (js) 中添加相应的配置和监听函数:

 data() {
    return {
      virtualConfig: {
        showNum: 15, //显示几条数据
        start: 0, //滚动过程显示的开始索引
        itemHeight: 63,
        len: 0,
        isChangeWidth: false,
      }, // 模板配置
      list: [] // 表格要显示的数据
    }
  },
  computed: {
    // 虚拟列表是否有滚动条, 处理头部高度多 一个滚动条宽度 问题
    isScoll() {
      return function () {
        const elListView = document.getElementById('list_view')
        if (elListView) {
          const elScrollArea = elListView.firstChild
          if (elListView.clientHeight < elScrollArea.clientHeight) {
            return true
          }
        }
        return false
      }
    },
    // 内容总高度
    scrollAreaHeight() {
      const me = this
      return function () {
        return me.virtualConfig.len * me.virtualConfig.itemHeight + 'px'
      }
    },
    // 内容总宽度
    scrollAreaWidth() {
      const me = this
      return function () {
        const elVirtualHeader = document.getElementById('virtual_header')
        me.virtualConfig.isChangeWidth = false
        if (elVirtualHeader) {
          return elVirtualHeader.clientWidth + 'px'
        }
        return '100%'
      }
    },
    // 前端分页过滤器
    pagingData() {
      const me = this
      return function (isVirtualRender = false) {
        // 通过子表数据路径获取分页数据
        let list = me.list
        if (isVirtualRender) {
            // window.addEventListener('resize', () => {
            //   //窗口改变时
            //   me.$nextTick(() => {
            //     me.handleScroll()
            //   })
            // })
          me.virtualConfig.len = list.length
          let data = list.slice(
            me.virtualConfig.start,
            me.virtualConfig.start +
              me.virtualConfig.showNum
          )
          return data
        } else {
          return list
        }
      }
    },
  },
  methods: {
    // 使用虚拟列表渲染后,计算滚动区域的数据索引
    computescrollArea(scrollTop, elId) {
      scrollTop = scrollTop || 0
      const elE = window.document.getElementById(elId)
      if (elE) {
        let vlopt = this.virtualConfig
        // 获取每个item的高度
        const tr = elE.querySelector('table > tbody > tr')
        if (tr) {
          vlopt.itemHeight = tr.getBoundingClientRect().height
        }
        vlopt.showNum = Math.ceil(elE.clientHeight / vlopt.itemHeight) // 取得可见区域的可见列表项数量
        // 开始的数组索引 : 滚到第几条数据 = 滚动高度 / 每个item的高度
        let newStart = Math.floor(scrollTop / vlopt.itemHeight)
        if (newStart + vlopt.showNum <= vlopt.len) {
          vlopt.start = newStart
          //绝对定位对相对定位的偏移量   已滚动的高度 = 滚到第几条数据  * 每个item的高度
          if (this.$refs['content_data']) {
            this.$refs[
              'content_data'
            ].style.webkitTransform = `translate3d(0, ${
              vlopt.start * vlopt.itemHeight
            }px, 0)`
          }
        }
      }
    },
    // 子表使用虚拟列表渲染后,滚动触发事件
    handleScroll() {
        const elE = window.document.getElementById('list_view')
        // 获取已滚动的高度
        const scrollTop = elE.scrollTop
        this.computescrollArea(scrollTop, 'list_view')
    },
    // 获取虚拟列表的序号
    getPagingDataIndex(index) {
        return index + this.virtualConfig.start
    },
    // 子表列宽拖动的鼠标按下事件
    handleMouseDown(event) {
      // 开始拖拽
      this.dragging = true
      // 当前拖拽的列所在的表格
      let tableEl = event.target
      // 当前所在列(单元格)
      let thEL = event.target
            // ......
          const index = thEL.getAttribute('data-index')
          const col = tableEl
            .querySelector('colgroup')
            .getElementsByTagName('col')[index]
          thEL.style.width = finalColumnWidth + 'px'
          if (col) {
            // 虚拟渲染时,需要设置对应的 col 的宽度
            col.style.width = thEL.style.width // 以 col 设置为准
          }

            // ......

          // 如果是虚拟列表渲染的话,头和身子是分开的
          let listView = tableEl.nextElementSibling
          if (listView && listView.classList.contains('list_view')) {
            let listTL = listView.querySelector('table')
            let listCol = listTL
              .querySelector('colgroup')
              .getElementsByTagName('col')[index]
            if (listCol) {
              listCol.style.width = thEL.style.width
            }
            if (listTL && isChange) {
              listTL.style.width = tableEl.style.width
            }
            this.virtualConfig.isChangeWidth = true
          }
                // ......
    },
  }

4、遇到问题:

一、为何没有使用 vue-virtual-scroller 插件?

由于需求虚拟渲染的标签是 tbody 中的 v-for 列表渲染每行数据 tr,该插件是直接利用 div 进行包裹或者tbody 中包裹 div ,没法实现表格布局的显示,故选择放弃使用该插件。

二、表格头部放到可视区域里的问题?

表格头部放到可视区域,会导致头部跟着一起滚动,而且会闪动,对使用不够友好。由于不能在 tbody 上直接使用 div 标签,会导致表格布局失效,故使用了一个表格实现表头,一个表格实现表身,能够实现表身可以自由 scroll。

三、表头表身分离了,列宽如何同步,横向滚动会造成两个滚动条问题?

1)表头表身的 table 都添加 colgroup 来保证列宽相同,在 colresize 的时候同步设置两部分的 colgroup 中的样式 width 配置。

2)表身有一个 div (可视区域)包裹,如果拖动列宽操作,可能会导致 此div 出现横向滚动条,另外,包裹 表头和表身的 div 也会出现横向滚动条,则会出现两个滚动条。如下图:

image

通过设置 [可视区域div] 宽度等于当前表头的宽度就可以实现 [可视区域div] 的横向滚动条不出现,并且通过外部 div 的横向滚动条可以同步滚动表头表身。

四、表身如果在滚动,那么表身宽度比表头多一个滚动条的宽度, 如何监听是否在滚动?

用可视区域和可滚动区域的高度比较就可以知道是否在滚动状态。

!该文章仅用于学习!

相关文章

  • 表格虚拟列表渲染的实现

    参考链接如下: https://blog.csdn.net/ZYS10000/article/details/12...

  • uniapp 开发app万条数据渲染不卡顿

    虚拟列表 只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染,以实现减少消耗,提高用户体验的技术。它是长列...

  • 虚拟列表优化

    虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高...

  • HTML练习——列表与表格

    练习: 1、说明下面这个列表是如何实现的:列表2、说明以下表格是如何实现的:表格 练习一 练习一主要是对于列表的练...

  • 小白学html-试卷实现(三)

    前面我们已经用html实现了列表和表格,戳这里列表实现、表格实现。今天,我们的任务是设计这样一张试卷: 分析: 这...

  • VUE生成dom后执行函数

    需求,动态生成列表数据,列表中有echarts表格 echarts渲染图形时,需要当然dom已经生成 调用API获...

  • useVirtualList

    简介 1 .虚拟化列表能力的hook,用于展示海量数据渲染时首屏渲染缓慢和卡顿的问题 扩展 1 .和无限列表相结合...

  • HTML基础之表格

    明确目标 我们需要实现这样的表格 任务列表 1.设定表格边框和加上标题2.实现一个类似目标表格的表格3.合并表格以...

  • cocos creator 虚拟列表

    虚拟列表是按需显示的一种技术,可以根据用户的滚动,不必渲染所有列表项,而只是渲染可视区域内的一部分列表元素的技术。...

  • 前端长列表解决方案

    实现思路 在讲解下面的内容之前,先对虚拟列表做一个简单的定义。 因为 DOM 元素的创建和渲染需要的时间成本很高,...

网友评论

      本文标题:表格虚拟列表渲染的实现

      本文链接:https://www.haomeiwen.com/subject/hgrutdtx.html