虚拟列表 | 青训营笔记

94 阅读3分钟

这是我参与「第四届青训营」笔记创作活动的的第4天

什么是虚拟列表?

在正文之前,先对虚拟列表做个简单的定义。

虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。

简而言之,虚拟列表指的就是「可视区域渲染」的列表。有三个概念需要了解一下:

  • 滚动容器元素:一般情况下,滚动容器元素是 window 对象。然而,我们可以通过布局的方式,在某个页面中任意指定一个或者多个滚动容器元素。只要某个元素能在内部产生横向或者纵向的滚动,那这个元素就是滚动容器元素考虑每个列表项只是渲染一些纯文本。在本文中,只讨论元素的纵向滚动。
  • 可滚动区域:滚动容器元素的内部内容区域。假设有 100 条数据,每个列表项的高度是 50,那么可滚动的区域的高度就是 100 * 50。可滚动区域当前的具体高度值一般可以通过(滚动容器)元素的 scrollHeight 属性获取。用户可以通过滚动来改变列表在可视区域的显示部分。
  • 可视区域:滚动容器元素的视觉可见区域。如果容器元素是 window 对象,可视区域就是浏览器的视口大小(即视觉视口);如果容器元素是某个 div 元素,其高度是 300,右侧有纵向滚动条可以滚动,那么视觉可见的区域就是可视区域。 建议参考下图理解一下上面的步骤: image.png

为什么需要虚拟列表?

虚拟列表是对长列表的一种优化方案。在前端开发中,会碰到一些不能使用分页方式来加载列表数据的业务形态,我们称这种列表叫做长列表。比如,在一些外汇交易系统中,前端会准实时的展示用户的持仓情况(收益、亏损、手数等),此时对于用户的持仓列表一般是不能分页的。

部分代码:

<template>
  <div
    class="content_box"
    ref="scrollTop"
    @scroll="scroll"
    :style="{ height: `${contentHeight}px` }"
  >
    //............................................
      <div class="container">
        <div
          class="row index"
          :style="{
            height: `${itemHeight * listAll.length - 100}px`,
            position: 'relative',
          }"
        >
          <div
            class="col-sm-8 list-container list-group"
            :style="{ position: 'absolute', top: `${top}px` }"
          >
            <li
              class="list-group-item"
              v-for="(item, index) in showList"
              :key="index"
            >
              <div class="meta-container">
                <span class="user-message"> {{ item.author }} </span>
                <saan>|</saan>
                <saan class="date"> {{ item.date.slice(0, 16) }}</saan>
              </div>
              <p class="infobox">
                <a href="#" target="_blank" class="title">{{ item.title }}</a>
                <a href="#">{{ item.text.slice(0, 80) }}</a>
              </p>
            </li>
          </div>
    //............................................
</template>

<script>
export default {
  name: "index",
  data() {
    return {
      listAll: [], //所有数据
      showList: [], //可视区域显示的数据
      contentHeight: 1080, //可视区域高度
      itemHeight: 141, //每条数据所占高度
      showNum: 0, //可视区域显示的最大条数
      top: 0, //偏移量
      scrollTop: 0, //卷起的高度
      startIndex: 0, //可视区域第一条数据的索引
      endIndex: 0, //可视区域最后一条数据的后面一条索引
    };
  },
  methods: {
    getList() {
      console.log("请求数据了");
      this.$axios
        .post("/api/list", {
          Teamnumber: "71744",
        })
        .then((response) => {
          if (response.data.status == 200) {
            this.listAll = response.data.results.reverse();
            console.log(this.listAll);
            //让showList在挂载时有值
            this.getShowList();
          }
        });
    },
    //计算可视区域数据
    getShowList() {
      this.showNum = Math.ceil(this.contentHeight / this.itemHeight); //可视区域最多出现的数据条数,值是小数的话往上取整,因为极端情况是第一条和最后一条都只显示一部分
      this.startIndex = Math.floor(this.scrollTop / this.itemHeight); //可视区域第一条数据的索引
      this.endIndex = this.startIndex + this.showNum; //可视区域最后一条数据的后面那条数据的索引
      this.showList = this.listAll.slice(this.startIndex, this.endIndex); //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
      const offsetY = this.scrollTop - (this.scrollTop % this.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
      this.top = offsetY;
      if (this.scrollTop >= 200 && window.innerWidth > 765) this.top -= 50;

      //如果大于页面高度,则让广告显示
      if (this.scrollTop > 680) {
        this.$refs.adv.style.display = "block";
      } else {
        this.$refs.adv.style.display = "none";
      }
    },
    //监听滚动事件,实时计算scrollTop
    scroll() {
      this.scrollTop = this.$refs.scrollTop.scrollTop;
      this.getShowList();
    },
  },
  mounted() {
    this.getList();
    this.scroll();
  },
};
</script>

总结:

滚动事件监听,计算每个item高度, 计算滚动的距离, 计算start end ,做数据截取,展示截取后的数据, 同时做样式绝对定位就可以 (top值就可以改变处于的高度)