虚拟滚动

虚拟滚动原理图

只有数据再可视区域内时 才会加载 在可视区域中 永远只展示几条数据 对数据多的无线下拉功能做了优化

为什么要使用虚拟滚动

  1. 如果一直加载到底, 那么最终还是会出现大量的DOM节点,导致滚动不流畅。
  2. 想要定位到某一个位置的数据会非常困难。
  3. 滚动条无法正确反映操作者当前浏览的信息在全部列表中的位置。而且大量数据加载,一次给我加载十几条,滚到底太慢了。

父组件

<template>
  // 调用子组件 传数组
  <myVirtualScroller :items="list"></myVirtualScroller>
</template>

<script>
  // 引入子组件
import myVirtualScroller from "./scrollView.vue";
// 模拟一个长列表
const list = [];
// 模拟一万条数据
for (let i = 0; i < 10000; i++) {
  list.push({
    id: i,
    label: `virtual-list ${i}`,
  });
}
export default {
  components: {
    myVirtualScroller,
  },
  data() {
    return {
      list: list,
    };
  },
};
</script>
<style scoped>
.container {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

子组件

// 传入数据可以实现虚拟滚动事件
// 这是一个封装好的子组件 传入数组进行渲染
<template>
  <!-- 最底层的可视区容器 -->
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <!-- 中间的可滚动区域,z-index=-1,高度和真实列表相同,目的是出现相同的滚动条 -->
    <div
      class="infinite-list-phantom"
      :style="{ height: listHeight + 'px' }"
    ></div>
    <!-- 最上层的可视区列表,数据和偏移距离随着滚动距离的变化而变化 -->
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px' }"
      >
        {{ item.label }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "MyVirtualList",
  props: {
    //父组件传过来的数据
    items: {
      type: Array,
      default: () => [],
    },
    //可视区域每一项的高度 没有传参的话默认高度是100px
    itemSize: {
      type: Number,
      //没有高度的话 高度会默认是100px
      default: 100,
    },
  },
  computed: {
    //列表总高度
    listHeight() {
      // 计算当前传值数据的总长度 * 每个盒子的高度 计算出所有数据的整体高度
      return this.items.length * this.itemSize;
    },
    //可视区列表的项数
    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemSize);
    },
    //可视区列表偏移距离对应的样式
    getTransform() {
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取可视区列表数据
    visibleData() {
      // 截取出可视区域显示的盒子 进行渲染
      return this.items.slice(
        this.start,
        Math.min(this.end, this.items.length)
      );
    },
  },
  mounted() {
    // 获取可是区域的高度
    this.screenHeight = this.$refs.list.clientHeight;
    // 开始
    this.start = 0;
    // 结束
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      screenHeight: 0, //可视区域高度
      startOffset: 0, //偏移距离
      start: 0, //起始索引
      end: 0, //结束索引
    };
  },
  methods: {
    scrollEvent() {
      //当前滚动条滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      //此时的开始索引 使用当前滚动条高度 / 盒子高度 拿到的是当前是哪一个盒子为第一个
      this.start = Math.floor(scrollTop / this.itemSize);
      //此时的结束索引
      //获取到当前第一个盒子的数值 加 计算好可视区域可容纳多少盒子的值
      this.end = this.start + this.visibleCount;
      //此时的偏移距离
      this.startOffset = scrollTop - (scrollTop % this.itemSize);
    },
  },
};
</script>

<style scoped>
.infinite-list-container {
  height: 100%;
  overflow: auto;
  position: relative;
}

.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

.infinite-list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
}

.infinite-list-item {
  line-height: 50px;
  text-align: center;
  color: #555;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
</style>

 

目前虚拟列表已经有很多知名的库,如 vue-virtual-scroller、vue-virtual-scroll-list、react-virtualized 等,
vue-virtual-scroller 这个虚拟列表库
RecycleScroller组件
适用于列表每一项高度确定的情况,高度可设置成相同,也可单独配置每一项高度
安装
npm install --save vue-virtual-scroller

 src/components/virtualRecycleScroller.vue

<template>
  <RecycleScroller
    class="scroller"
    :items="items"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="desc">
      {{ item.label }}
    </div>
  </RecycleScroller>
  <!-- items: 需要渲染的列表,itemSize: 列表项的高度,keyField: 列表循环的key值 -->
</template>

<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
  components: {
    RecycleScroller
  },
  props: {
    items: Array
  }
}
</script>

<style scoped>
.scroller {
  height: 100%;
}
.desc {
  height: 50px;
  line-height: 50px;
  text-align: center;
  box-sizing: border-box;
  border: 1px solid #ccc;
}
</style>

src/App.vue

template>
  <div class="container">
    <virtual-recycle-scroller :items="list" />
  </div>
</template>

<script>
import virtualRecycleScroller from '@/components/virtualRecycleScroller'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
  list.push({
    id: i,
    label: `virtual-list ${i}`
  })
}
export default {
  components: {
    virtualRecycleScroller
  },
  data() {
    return {
      list: list
    }
  }
}
</script>

<style scoped>
.container {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

DynamicScroller组件

适用于列表每一项高度不确定的情况

src/components/virtualDynamicScroller.vue

<template>
  <DynamicScroller class="scroller" :items="items" :min-item-size="50">
    <template v-slot="{ item, index, active }">
      <DynamicScrollerItem
        :item="item"
        :active="active"
        :size-dependencies="[item.label]"
        :data-index="index"
      >
        <div class="desc">{{ item.label }}</div>
      </DynamicScrollerItem>
    </template>
  </DynamicScroller>
  <!-- minItemSize: 列表项初次渲染使用的最小高度-->
  <!-- active: 保持视图,防止不必要的重新计算 -->
  <!-- sizeDependencies: 影响高度的值,如果发生变化,则重新计算 -->
</template>

<script>
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
  components: {
    DynamicScroller,
    DynamicScrollerItem
  },
  props: {
    items: Array
  }
}
</script>

<style scoped>
.scroller {
  height: 100%;
}
.desc {
  padding: 12px;
  text-align: center;
  border: 1px solid #ccc;
}
</style>

src/App.vue

<template>
  <div class="container">
    <virtual-dynamic-scroller :items="list" />
  </div>
</template>

<script>
import virtualDynamicScroller from '@/components/virtualDynamicScroller.vue'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
  list.push({
    id: i,
    label: `virtual-scroller ${i}`
  })
}
// 模拟一个内容不同的列表项
list[2].label = `virtual-scroller 2 子豪 子豪 子豪 子豪 子豪 子豪 子豪 
子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪 子豪`
export default {
  components: {
    virtualDynamicScroller
  },
  data() {
    return {
      list: list
    }
  }
}
</script>

<style scoped>
.container {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值