账号密码登录
微信安全登录
微信扫描二维码登录

登录后绑定QQ、微信即可实现信息互通

手机验证码登录
找回密码返回
邮箱找回 手机找回
注册账号返回
其他登录方式
分享
  • 收藏
    X
    VUE,ElementUI列表大数据卡顿问题?(已解决)
    24
    0

    基于elementUI的table,在不修改源码的情况下支持大数据了渲染的场景

    2019-7-18又优化了一版(后面遇到BUG,就直接更改这个了),相当于重写,用了指令的一些特性;完善了下注释

    
    var EventUtil = {
      /*检测绑定事件*/
      addHandler: function (element, type, handler) {
        if (element.addEventListener) {
          element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
          element.attachEvent('on' + type, handler);
        } else {
          element["on" + type] = handler /*直接赋给事件*/
        }
      },
      /*通过removeHandler*/
      removeHandler: function (element, type, handler) {
        /*Chrome*/
        if (element.removeEventListener)
          element.removeEventListener(type, handler, false);
        else if (element.deattachEvent) {
          /*IE*/
          element.deattachEvent('on' + type, handler);
        } else {
          element["on" + type] = null;
          /*直接赋给事件*/
        }
      }
    };
    
    // 设置默认溢出显示数量
    var spillDataNum = 5;
    
    // 设置隐藏函数
    let timeout = false;
    let triggerDataIndexChange = function (el, binding, vnode, oldVnode, currentTableHeight) {
      if (timeout) {
        clearTimeout(timeout);
      }
      const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');
      const virtualDiv = elTableBodyWrapper.querySelector('div.virtual-div');
      const elTableBody = elTableBodyWrapper.querySelector('table.el-table__body');
      const elTableRow = elTableBodyWrapper.querySelector('table tr.el-table__row');
      const context = vnode.context;
      const tableData = context.tableData;
    
      const elTableBodyWrapperHeight = elTableBodyWrapper.clientHeight;
      const elTableRowHeight = elTableRow.clientHeight;
      const showRowNum = Math.round(elTableBodyWrapperHeight / elTableRowHeight);
    
      let maxScrollTop = virtualDiv.clientHeight - elTableBodyWrapper.clientHeight - spillDataNum * elTableRowHeight;
      // 处理滚动条置顶的情况
      if (elTableBodyWrapper.scrollTop === 0) {
        context.currentStartIndex = 0;
        context.currentEndIndex = tableData.length + spillDataNum;
        elTableBody.setAttribute('style', 'transform: translateY(0px);width: ' + elTableBody.style.width + ';');
        return;
      }
    
      // 处理滚动条滚动中和置低的情况
      let scrollTopNum = elTableBodyWrapper.scrollTop / elTableRowHeight;
      let currentStartIndex = scrollTopNum;
      let currentEndIndex = scrollTopNum + showRowNum + spillDataNum;
      if (currentEndIndex - tableData.length >= 0) {
        context.currentStartIndex = tableData.length - 1 - showRowNum - spillDataNum;
        context.currentEndIndex = tableData.length - 1;
        elTableBody.setAttribute('style', 'transform: translateY(' + maxScrollTop + 'px);width: ' + elTableBody.style.width + ';');
        return;
      }
      context.currentStartIndex = currentStartIndex;
      context.currentEndIndex = currentEndIndex;
      elTableBody.setAttribute('style', 'transform: translateY(' + (elTableBodyWrapper.scrollTop) + 'px);width: ' + elTableBody.style.width + ';');
    };
    
    // 滚动条置低
    let handleScrollEnd = function (el) {
      const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');
      elTableBodyWrapper.scrollTop = elTableBodyWrapper.scrollHeight;
    };
    
    export default {
      name: 'loadmore',
      inserted: function (el, binding, vnode, oldVnode) {
        const isLoadmore = vnode.data.attrs['is-loadmore'];
        if (isLoadmore === 'NoScrollBar') {
          return;
        }
        const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');
        const elTableBodyWrapperChildren = elTableBodyWrapper.children;
        // 创建虚拟DIV,用来设置数据高度,触发滚动条高度更新
        const virtualDiv = document.createElement('div');
        virtualDiv.setAttribute('class', "virtual-div");
        elTableBodyWrapper.appendChild(virtualDiv);
        virtualDiv.appendChild(elTableBodyWrapperChildren[0]);
        // 滚动事件
        let handleMoveScroll = function () {
          triggerDataIndexChange(el, binding, vnode, oldVnode);
        }
        EventUtil.addHandler(elTableBodyWrapper, "scroll", handleMoveScroll);
      },
      update() {},
      componentUpdated: function (el, binding, vnode, oldVnode) {
        // 获取组件缓存的数据
        const dataSize = vnode.data.attrs['data-size'];
        const oldDataSize = oldVnode.data.attrs['data-size'];
        const isLoadmore = vnode.data.attrs['is-loadmore'];
        const isAddRowData = vnode.data.attrs['is-addRowData'];
    
        // 获取必要的DOM节点
        const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');
        const elTableRow = elTableBodyWrapper.querySelector('table tr.el-table__row');
        let elTableRowHeight = 0;
    
        if (elTableRow) {
          elTableRowHeight = elTableRow.clientHeight;
        }
        if (elTableRowHeight === 0) {
          elTableRowHeight = 25;
        }
    
        // 如果是设置不需要滚动条的模式,直接显示所有数据,并设置表格的高度为数据的高度
        if (isLoadmore === 'NoScrollBar') {
          const context = vnode.context;
          let tableHeight = (dataSize + 1) * elTableRowHeight + 40;
          context.currentTableHeight = tableHeight;
          return;
        }
    
        if (dataSize) {
          // 根据行高计算当前应该显示那些数据
          // 根据数据多少和行高设置虚拟滚动DIV的高度
          const virtualDiv = elTableBodyWrapper.querySelector('div.virtual-div');
          const virtualDivHeight = dataSize * elTableRowHeight;
          virtualDiv.setAttribute('style', `height: ${virtualDivHeight}px;`);
          // 首次进来,即currentStartIndex为零,如果可显示区域大于默认显示20条数据。要重新赋值默认显示的数量为显示区域的数量加上默认溢出的数量
          const bodyHeight = vnode.componentInstance.layout.height;
          const showRowNum = Math.round(bodyHeight / elTableRowHeight);
          const currentStartIndex = vnode.context.currentStartIndex;
          const currentEndIndex = vnode.context.currentEndIndex;
          if (currentStartIndex === 0 && bodyHeight && showRowNum > currentEndIndex) {
            vnode.context.currentEndIndex = showRowNum + spillDataNum;
          }
        }
    
        // 新增数据处理,直接将滚动条置底
        if (isAddRowData) {
          handleScrollEnd(el);
        }
      }
    };
    

    问题描述

    很早之前的问题,后来根据网友的回答,又优化了一些版本。今天整理下,感谢寻梦无痕提供的帮助

    场景

    前端UI框架使用的是ElementUI,项目要求数据不分页一个表格至少要1000条数据,这时点击其他DOM操作,会出现卡顿的现象。如点击复选框。

    现状

    已解决3000条数据点击其他DOM操作卡顿的问题。

    求助

    助如何解决初次放入数据时卡顿的情况?(内附解决加载完成后卡顿的思路)**

    结果分享

    优化了大概三个版本,保留第一次的分享的,后面直接贴出最终版

    解决3000条数据点击其他DOM操作卡顿的思路

    通过指令添加表格滚动后的事件,设置非显示区域的其他DOM节点隐藏。

    指令代码

    // 设置隐藏函数
    var timeout = false;
    let setRowDisableNone = function (selectRows, topNum, rowHeight, showRowNum) {
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(() => {
        selectRows.forEach((selectRow, index) => {
          const selectRowsTds = selectRow.querySelectorAll('td');
          if (topNum <= index && index <= (topNum + showRowNum)) {
            selectRowsTds.forEach((selectRowsTd) => {
              selectRowsTd.style.display = '';
            });
          } else {
            selectRow.style.height = `${rowHeight}px`;
            selectRowsTds.forEach((selectRowsTd) => {
              selectRowsTd.style.display = 'none';
            });
          }
        });
      }, 100);
    };
    
    export default {
      name: 'loadmore',
      componentUpdated: function (el) {
        setTimeout(() => {
          const selectWrap = el.querySelector('.el-table__body-wrapper');
          const selectRow = selectWrap.querySelector('table tr');
          const rowHeight = selectRow.clientHeight;
          const selectRows = selectWrap.querySelectorAll('table tr');
          setRowDisableNone(selectRows, 0, rowHeight, showRowNum);
          // 监听滚动事件,操作DOM隐藏和显示
          selectWrap.addEventListener('scroll', function () {
            let topPx = this.scrollTop;
            let topNumber = Math.round(topPx / rowHeight);
            let rowTopLastIndex = topNumber;
            setRowDisableNone(selectRows, rowTopLastIndex, rowHeight, showRowNum);
          })
        });
      }
    };

    有同学说componentUpdated执行不了,这个我是通过指令做的,后面我贴出完成的代码和例子

    解决性能问题(最终版)

    参考评论区寻梦无痕分享的例子,整合以下,感谢。

    思路

    减少对DOM节点的渲染,通过滚动函数节流实现滚动后事件来动态渲染数据

    Element Table代码

    <template>
      <div>
        <el-table border :data="filteredData" style="width: 100%" height="300" :data-size="tableData.length" v-loadmore="handelLoadmore">
          <el-table-column type="selection" width="55">
          </el-table-column>
          <el-table-column label="日期" width="180">
            <template slot-scope="scope">
              <div>
                <i class="el-icon-time"></i>
                <span style="margin-left: 10px">{{ scope.row.date }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="日期" width="180">
            <template slot-scope="scope">
              <div>
                <i class="el-icon-time"></i>
                <span style="margin-left: 10px">{{ scope.row.date }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="日期" width="180">
            <template slot-scope="scope">
              <div>
                <i class="el-icon-time"></i>
                <span style="margin-left: 10px">{{ scope.row.date }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="日期" width="180">
            <template slot-scope="scope">
              <div>
                <i class="el-icon-time"></i>
                <span style="margin-left: 10px">{{ scope.row.date }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="姓名" width="180">
            <template slot-scope="scope">
              <div>
                <el-popover trigger="hover" placement="top">
                  <p>姓名: {{ scope.row.name }}</p>
                  <p>住址: {{ scope.row.address }}</p>
                  <div slot="reference" class="name-wrapper">
                    <el-tag size="medium">{{ scope.row.name }}</el-tag>
                  </div>
                </el-popover>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="操作">
            <template slot-scope="scope">
              <div>
                <el-button size="mini"  @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
                <el-button size="mini" type="danger"  @click="handleDelete(scope.$index, scope.row)">删除</el-button>
              </div>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'test',
      components: {},
      data () {
        return {
          tableData: [],
          currentStartIndex: 0,
          currentEndIndex: 20
        };
      },
      created () {
        this.getTableData();
      },
      computed: {
        filteredData () {
          return this.tableData.filter((item, index) => {
            if (index < this.currentStartIndex) {
              return false;
            } else if (index > this.currentEndIndex) {
              return false;
            } else {
              return true;
            }
          });
        }
      },
      methods: {
        handelLoadmore (currentStartIndex, currentEndIndex) {
          this.currentStartIndex = currentStartIndex;
          this.currentEndIndex = currentEndIndex;
        },
        getTableData () {
          let cont = 0;
          let tableData = [];
          while (cont < 30000) {
            cont = cont + 1;
            let object = {
              date: cont,
              name: '王小虎' + cont,
              address: '上海市普陀区金沙江路 cont 弄'
            }
            tableData.push(object);
          }
          setTimeout(() => {
            this.tableData = tableData;
          }, 2000);
        }
      },
      watch: {}
    }
    </script>
    
    <style scoped>
    .el-table__body-wrapper .el-table__row td {
      display: none;
    }
    .el-table__body-wrapper .el-table__row {
      height: 38px;
    }
    </style>

    指令代码

    // 设置默认溢出显示数量
    var spillDataNum = 20;
    
    // 设置隐藏函数
    var timeout = false;
    let setRowDisableNone = function (topNum, showRowNum, binding) {
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(() => {
        binding.value.call(null, topNum, topNum + showRowNum + spillDataNum);
      });
    };
    
    export default {
      name: 'loadmore',
      componentUpdated: function (el, binding, vnode, oldVnode) {
        setTimeout(() => {
          const dataSize = vnode.data.attrs['data-size'];
          const oldDataSize = oldVnode.data.attrs['data-size'];
          if(dataSize === oldDataSize){
            return;
          }
          const selectWrap = el.querySelector('.el-table__body-wrapper');
          const selectTbody = selectWrap.querySelector('table tbody');
          const selectRow = selectWrap.querySelector('table tr');
          if (!selectRow) {
            return;
          }
          const rowHeight = selectRow.clientHeight;
          let showRowNum = Math.round(selectWrap.clientHeight / rowHeight);
    
          const createElementTR = document.createElement('tr');
          let createElementTRHeight = (dataSize - showRowNum - spillDataNum) * rowHeight;
          createElementTR.setAttribute('style', `height: ${createElementTRHeight}px;`);
          selectTbody.append(createElementTR);
    
          // 监听滚动后事件
          selectWrap.addEventListener('scroll', function () {
            let topPx = this.scrollTop - spillDataNum * rowHeight;
            let topNum = Math.round(topPx / rowHeight);
            let minTopNum = dataSize - spillDataNum - showRowNum;
            if (topNum > minTopNum) {
              topNum = minTopNum;
            }
            if (topNum < 0) {
              topNum = 0;
              topPx = 0;
            }
            selectTbody.setAttribute('style', `transform: translateY(${topPx}px)`);
            createElementTR.setAttribute('style', `height: ${createElementTRHeight-topPx > 0 ? createElementTRHeight-topPx : 0}px;`);
            setRowDisableNone(topNum, showRowNum, binding);
          })
        });
      }
    };

    注释比较简单,要注释的地方太多了,就看代码吧。还有很多值得优化的地方。

    代码中使用的是全局注册指令,我就不贴代码了

    1
    打赏
    收藏
    点击回答
    您的回答被采纳后将获得:提问者悬赏的 10 元积分
        全部回答
    • 0
    更多回答
    扫一扫访问手机版
    • 回到顶部
    • 回到顶部