不到万不得已不建议使用虚拟列表,建议使用分页和过滤器。

但有些时候事与愿违,功能就必须要把列表全展示下来,列表多达上万条数据,并且字段高达100,这样下来接口访问10s以上,就更别说页面渲染的压力了,没办法只能用虚拟列表吧,废话不多说,直接开干。

1.原理,借用网图

 思路也比较明了,一共有n条数据,但我们只渲染可视区域的局部数据,通过滚动列表从而得知当前滚动的距离,计算出开始索引,从而获取新的展示区域的局部数据。

2.准备条件

开始索引:start,用于总数据切片头部

结束索引:end ,用于总数据切片尾部

每行表格行高:ItemHeight,(这边需求是等高的列表)

可视区域的Item数量:num ,(最多展示多少行)

3.开始实现

<div ref="listWrap" style="height:100px;overflow-y: scroll;" @scroll="scrollListener">
  <div ref="list">
    <el-table ref="scrollTable" v-loading="tableLoading" :data="showList">
            ...
    </el-table>
  </div>
</div>
....
data(){
    return {
      tableData:[], // 总共的列表数据
      itemHeight: 40, // item高度
      num: 10, // 展示的数据
      start: 0, // 开始索引
      end: 9 // 结束索引
    }
}
mounted() {
    this.$refs.listWrap.style.height = '100px' // 设置可视区域的高度
},
watch:{
    length(val){
        // 超过10行数据,就按照最大40*10 400px高度的列表就行
        if (val >= 10) {
          this.$refs.listWrap.style.height = this.itemHeight * this.num+ 'px' 
        } else {
        // 不足10行数据,这边 加57是因为表头的高度,具体情况,你们加不加无所谓
          this.$refs.listWrap.style.height = this.itemHeight * val + 57 + 'px'
        }
    }
},
computed: {
    // 获取展示的列表
    showList() {
      return this.tableData.slice(this.start, this.end)
    },
    // 获取列表长度
    length() {
      return this.tableData.length || 0
    }
  },
methods: {
    scrollListener() {
      // 获取滚动高度
      const scrollTop = this.$refs.listWrap.scrollTop
      // 开始的数组索引
      this.start = Math.floor(scrollTop / this.itemHeight)
      // 结束索引
      this.end = this.start + this.num
      this.$refs.list.style.transform = `translateY(${this.start * this.itemHeight}px)`// 对列表项y轴偏移
    },
    // ajax获取 tableData
    getData(){
        xxx().then(()=>{
            .... 
            this.tableData = xxx
        }).catch()
    }
  }

设置初始盒子高度100px,然后获得总数据之后,通过计算属性计算出展示的数据数量不足10个按照个数计算展示高度即可,超过10个最多也是最大高度,我这边是400

这样简单的一个虚拟列表完善了,当然几千条数据 就算每行100个字段也不会这么卡,因为我这边还有好多输入框嵌套在表格中,不免会卡顿

4. 继续优化

当数据量过多的时候,用户需要拉到右侧去操作其他数据,这时候需要滑动到底部再看很影响用户体验,可以监听一个滚动条和el-table的横向滚动条滚动距离一致,然后将el-table自带的overflow-x:hidden !important,注意要加层级,这样可以增加用户体验

<div ref="listWrap" style="height:100px;overflow-y: scroll;" @scroll="scrollListener">
  <div ref="list">
    <el-table ref="scrollTable" v-loading="tableLoading" :data="showList">
            ...
    </el-table>
  </div>
</div>
<div ref="bottomScroll" class="bottom-scroll">
   <div class="bottom-scroll-content" :style="{ width: bottomScrollWidth }" />
</div>

...
watch:{
  bottomScrollWidth: {
    // 两个滚动条同时滚动
    handler(newVal, oldVal) {
      // 监听滚动条
      this.$nextTick(() => {
        this.tableDom = this.$refs.scrollTable.bodyWrapper
        this.tableDom.addEventListener('scroll', () => {
          // 与表格滚动条同步
          const scrollLeft = this.tableDom.scrollLeft
          this.$refs.bottomScroll.scrollTo(scrollLeft, 0)
        })
        const bottomScroll= this.$refs.bottomScroll
        bottomScroll.addEventListener('scroll', () => {
          // 与表格滚动条同步
          const scrollLeft = this.$refs.bottomScroll.scrollLeft
          this.$refs.scrollTable.bodyWrapper.scrollTo(scrollLeft, 0)
        })
      })
    },
    deep: true
  }
.... scoped
.bottom-scroll {
    width: 100%;
    overflow-x: auto;
  }
  .bottom-scroll-content {
    /* 高度不设置的话滚动条出不来 */
    height: 1px;
  }
// 让该页面的横向滚动条隐藏
/deep/.el-table--scrollable-x .el-table__body-wrapper{
    overflow-x: hidden !important;
  }
...

总代码如下

<div ref="listWrap" style="height:100px;overflow-y: scroll;" @scroll="scrollListener">
  <div ref="list">
    <el-table ref="scrollTable" v-loading="tableLoading" :data="showList">
            ...
    </el-table>
  </div>
</div>
<div ref="bottomScroll" class="bottom-scroll">
   <div class="bottom-scroll-content" :style="{ width: bottomScrollWidth }" />
</div>
....
data(){
    return {
      tableData:[], // 总共的列表数据
      itemHeight: 40, // item高度
      num: 10, // 展示的数据
      start: 0, // 开始索引
      end: 9 // 结束索引
    }
}
mounted() {
    this.$refs.listWrap.style.height = '100px' // 设置可视区域的高度
},
watch:{
    length(val){
        // 超过10行数据,就按照最大40*10 400px高度的列表就行
        if (val >= 10) {
          this.$refs.listWrap.style.height = this.itemHeight * this.num+ 'px' 
        } else {
        // 不足10行数据,这边 加57是因为表头的高度,具体情况,你们加不加无所谓
          this.$refs.listWrap.style.height = this.itemHeight * val + 57 + 'px'
        }
    },
bottomScrollWidth: {
    // 两个滚动条同时滚动
    handler(newVal, oldVal) {
      // 监听滚动条
      this.$nextTick(() => {
        this.tableDom = this.$refs.scrollTable.bodyWrapper
        this.tableDom.addEventListener('scroll', () => {
          // 与表格滚动条同步
          const scrollLeft = this.tableDom.scrollLeft
          this.$refs.bottomScroll.scrollTo(scrollLeft, 0)
        })
        const bottomScroll= this.$refs.bottomScroll
        bottomScroll.addEventListener('scroll', () => {
          // 与表格滚动条同步
          const scrollLeft = this.$refs.bottomScroll.scrollLeft
          this.$refs.scrollTable.bodyWrapper.scrollTo(scrollLeft, 0)
        })
      })
    },
    deep: true
  }
},
computed: {
    // 获取展示的列表
    showList() {
      return this.tableData.slice(this.start, this.end)
    },
    // 获取列表长度
    length() {
      return this.tableData.length || 0
    }
  },
methods: {
    scrollListener() {
      // 获取滚动高度
      const scrollTop = this.$refs.listWrap.scrollTop
      // 开始的数组索引
      this.start = Math.floor(scrollTop / this.itemHeight)
      // 结束索引
      this.end = this.start + this.num
      this.$refs.list.style.transform = `translateY(${this.start * this.itemHeight}px)`// 对列表项y轴偏移
    },
    // ajax获取 tableData
    getData(){
        xxx().then(()=>{
            .... 
            this.tableData = xxx
        }).catch()
    }
  }
.... scoped
.bottom-scroll {
    width: 100%;
    overflow-x: auto;
  }
  .bottom-scroll-content {
    /* 高度不设置的话滚动条出不来 */
    height: 1px;
  }
// 让该页面的横向滚动条隐藏
/deep/.el-table--scrollable-x .el-table__body-wrapper{
    overflow-x: hidden !important;
  }
...

效果如下 

 

这样可以实现多数据量也不会卡顿的情况,至于后端需要查询10多秒,就让后端优化sql什么的,但注意的是,一旦用虚拟列表,索引将不复存在,无法通过索引作为使用条件,因为可视区域下都是临时虚拟索引,一旦滚动条滚动,索引将毫无意义。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐