vue3页面数据过多使用虚拟列表

vue3高度不固定定使用虚拟列表展示数据(列表内容高度不固定)

公司代码优化发现因为数据量太多,造成操作加载页面非常卡顿,决定使用虚拟加载的方法进行优化,发现网上的虚拟列表插件只能使用固定高度,查了好久发现没法偷懒,自己写了一个
注意点:
(1)在html中,cell使用了绝对定位,定位的top值是由positionData中的top值来决定的,所以根据positionData数组中的top值,html也会跟着进行位置的重新定位
(2)盒子的滚动区域高度是由positionData数组的最后一项数据的boxHeight属性决定的,在开始时,只有预估的高度,所以盒子的滚动高度也是预估的。在positionData数组重新校对后,数组的每一项boxHeight也跟着校对,所以滚动盒子的真实高度也会根据实际的cell盒子高度变化而变化

1.html 下列各个属性必填,不然会出问题

		//下列各个属性必填,不然会出问题
          <div
              class="ruleDataList"
              ref="wrapper"
              @scroll="wrapperScroll($event)"
            >
             <div
                class="contentBox"
                :style="{ height: contentBoxHeight + 'px' }"
              >
             <div
                  ref="cellNode"
                  class="cell"
                  v-for="(item, index) in showList"
                  :style="{ top: positionData[item.renderIndex].top + 'px' }"
                  :data-index="item.renderIndex"
                  :key="item.rule_id"
                >
                ..............
                 </div>
              </div>
            </div>

2.css

.ruleDataList{
  height: calc(100vh - 406px);
   overflow: auto;
    .contentBox {
      position: relative;
      width: 100%;
      }
      .cell {
        position: absolute;
        left: 0;
        width: 100%;
      }
}

  1. js方法
    在页面渲染后先获取盒子高度containerDomHeight.value = wrapper.value.clientHeight;(一定要在数据渲染出来以后)
    在调用方法 initvVirtual()
// 展示的数据
const showList = ref([
  // {
  //   index,  // 位置索引
  //   data: '暂无数据'
  // }
]);
// 盒子高度
const containerDomHeight = ref(0);
const wrapper = ref(null);
const cellNode = ref(null);
// 每行高度(预估高度)
let cellHeight = ref(500);
// 可视框显示的数据量
const showCount = ref(10);
// 需要滚动的内容高度
const contentBoxHeight = computed(() => {
  return positionData.value[positionData.value.length - 1]?.boxHeight;
});
// 存储每一项的位置信息
const positionData = ref([
  // {
  //   index: 0,  // 位置索引
  //   top: 0, // 距离顶部的位置
  //   height: 0,  // 当前项的高度
  //   // 顶部的位置 + 当前项的高度 = 滚动盒子应用的高度
  //   boxHeight: 0
  // }
]);
// 初始化数据方法
function initvVirtual() {
  // 可视框需要显示的数据量
  showCount.value = Math.ceil(containerDomHeight.value / cellHeight.value);
  // 预估的位置信息
  positionData.value = ruleSeverityData.value.map((item, index) => {
    return {
      index: index,
      top: cellHeight.value * index,
      height: cellHeight.value,
      // 当前项为最后一条数据,滚动盒子的预估高度
      // top值加上本身的高度,就能得知滚动盒子的预估高度
      // boxHeight: index * cellHeight + cellHeight
      boxHeight: (index + 1) * cellHeight.value
    };
  });
  renderData();
}
//监听滚动事件
const wrapperScroll = e => {
  renderData();
};
// 获取需要渲染数据,默认渲染数据
function renderData() {
  // 滚动出去的距离
  let scrollTop = wrapper.value.scrollTop;

  // 可视区域第一行索引
  let startIndex = Math.max(getStartIndex(scrollTop) - 2, 0);
  // 可视区域最后一行索引
  let endIndex = Math.min(
    startIndex + showCount.value + 4,
    ruleSeverityData.value.length
  );
  // 获取需要渲染的数据
  showList.value = [];
  for (let i = startIndex; i < endIndex; i++) {
    ruleSeverityData.value[i].renderIndex = positionData.value[i].index;
    showList.value.push(ruleSeverityData.value[i]);
  }
  nextTick(() => {
    if (cellNode.value) {
      cellNode.value.forEach(node => {
        // 实际高度
        let height = node.getBoundingClientRect().height;
        let index = +node.dataset.index;
        let position = positionData.value[index];
        let cha = height - position.height;
        // console.log(cha)
        // 当前node的实际高度,跟预估高度不一致
        if (cha) {
          // 更新当前node的位置信息
          positionData.value[index].height = height;
          positionData.value[index].boxHeight += cha;
          for (let i = index + 1; i < positionData.value.length; i++) {
            // 更新当前node后面所有项的位置信息
            positionData.value[i].top = positionData.value[i - 1].boxHeight;
            positionData.value[i].boxHeight += cha;
          }
        }
      });
    }
  });
}
// 获取可视区域第一行索引
function getStartIndex(scrollTop) {
  // 暴力搜索
  // let startIndex = this.positionData.findIndex(item => {
  //   return item.boxHeight > scrollTop
  // })
  // 二分查找
  let left = 0,
    right = positionData.value.length - 1,
    res = right;
  while (left < right) {
    let mid = Math.floor((right - left) / 2) + left;
    if (positionData.value[mid].boxHeight === scrollTop) {
      // console.log(mid)
      return mid + 1;
    } else if (positionData.value[mid].boxHeight > scrollTop) {
      res = Math.min(mid, res);
      right = right - 1;
    } else if (positionData.value[mid].boxHeight < scrollTop) {
      left = mid + 1;
    }
  }
  return res;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值