目 录CONTENT

文章目录

虚拟滚动实现十万条数据不分页流畅加载不卡顿

萧瑟
2023-04-14 / 1 评论 / 31 点赞 / 1,233 阅读 / 6,241 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-04-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1、虚拟列表

虚拟列表是一种优化技术,用于在网页上渲染大量的数据,而不影响性能和用户体验。虚拟列表的原理是只渲染可视区域内的数据,而将其他数据隐藏或占位,从而减少 DOM 节点的数量和重绘次数。虚拟列表可以应用于表格、列表、下拉框等场景,提高网页的流畅度和响应速度。

原理图

效果图

虚拟列表

页面体验:https://demo.foreval.cn/virtualScrolling/

代码

1、html版本
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟滚动</title>
    <style>
        .virtualListWrap {
            box-sizing: border-box;
            width: 240px;
            border: solid 1px #000000;
            overflow-y: auto;
            position: relative;
        }

        .virtualListWrap .contentList {
            width: 100%;
            height: auto;
            position: absolute;
            top: 0;
            left: 0;
        }

        .virtualListWrap .contentList .itemClass {
            box-sizing: border-box;
            width: 100%;
            height: 40px;
            line-height: 40px;
            text-align: center;
        }

        .virtualListWrap .contentList .itemClass:nth-child(even) {
            background: #c7edcc;
        }

        .virtualListWrap .contentList .itemClass:nth-child(odd) {
            background: pink;
        }

        .virtualListWrap .loadingBox {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(255, 255, 255, 0.64);
            color: green;
            display: flex;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<body>

    <div id="app">

        <!-- 
            虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据
            比如窗口只能看到10条数据,一条40像素,10条400像素
            故,窗口的高度为400像素,注意要开定位和滚动条
         -->
        <div class="virtualListWrap" ref="virtualListWrap" @scroll="handleScroll"
            :style="{ height: itemHeight * count + 'px' }">
            <!-- 占位dom元素,其高度为所有的数据的总高度 -->
            <div class="placeholderDom" :style="{ height: allListData.length * itemHeight + 'px' }"></div>
            <!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
            <div class="contentList" :style="{ top: topVal }">
                <!-- 每一条(项)数据 -->
                <div v-for="(item, index) in showListData" :key="index" class="itemClass"
                    :style="{ height: itemHeight + 'px' }">
                    {{ item }}
                </div>
            </div>
            <!-- 加载中部分 -->
            <div class="loadingBox" v-show="loading">
                <i class="el-icon-loading"></i>
                &nbsp;&nbsp;<span>loading...</span>
            </div>
        </div>

    </div>

    <script>
        const { createApp } = Vue

        createApp({
            data() {
                return {
                    allListData: [], // 所有的数据,比如这个数组存放了十万条数据
                    itemHeight: 40, // 每一条(项)的高度,比如40像素
                    count: 10, // 一屏展示几条数据
                    start: 0, // 开始位置的索引
                    end: 10, // 结束位置的索引
                    topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动
                    loading: false,
                }
            },
            computed: {
                // 从所有的数据allListData中截取需要展示的数据showListData
                showListData: function () {
                    return this.allListData.slice(this.start, this.end);
                },
            },
            async created() {
                this.loading = true;
                this.allListData = Array.from({ length: 100000 }, (v, i) => `第 ${i + 1} 条数据`);
                this.loading = false;
            },
            methods: {
                // 滚动这里可以加上节流,减少触发频次
                handleScroll() {
                    /**
                     * 获取在垂直方向上,滚动条滚动了多少像素距离Element.scrollTop
                     *
                     * 滚动的距离除以每一项的高度,即为滚动到了多少项,当然,要取个整数
                     * 例:滚动4米,一步长0.8米,滚动到第几步,4/0.8 = 第5步(取整好计算)
                     *
                     * 又因为我们一次要展示10项,所以知道了起始位置项,再加上结束位置项,
                     * 就能得出区间了【起始位置, 起始位置 + size项数】==【起始位置, 结束位置】
                     * */
                    const scrollTop = this.$refs.virtualListWrap.scrollTop;
                    this.start = Math.floor(scrollTop / this.itemHeight);
                    this.end = this.start + this.count;
                    /**
                     * 动态更改定位的top值,确保联动,动态展示相应内容
                     * */
                    this.topVal = this.$refs.virtualListWrap.scrollTop + "px";
                },
            },

        }).mount('#app')
    </script>
</body>

</html>

2、template版本

<template>
  <!-- 虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据
            比如窗口只能看到10条数据,一条40像素,10条400像素
            故,窗口的高度为400像素,注意要开定位和滚动条 -->
  <div
    class="virtualListWrap"
    ref="virtualListWrap"
    @scroll="handleScroll"
    :style="{ height: itemHeight * count + 'px' }"
  >
    <!-- 占位dom元素,其高度为所有的数据的总高度 -->
    <div
      class="placeholderDom"
      :style="{ height: allListData.length * itemHeight + 'px' }"
    ></div>
    <!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
    <div class="contentList" :style="{ top: topVal }">
      <!-- 每一条(项)数据 -->
      <div
        v-for="(item, index) in showListData"
        :key="index"
        class="itemClass"
        :style="{ height: itemHeight + 'px' }"
      >
        {{ item }}
      </div>
    </div>
    <!-- 加载中部分 -->
    <div class="loadingBox" v-show="loading">
      <i class="el-icon-loading"></i>
      &nbsp;&nbsp;<span>loading...</span>
    </div>
  </div>
</template>
<script>
import axios from "axios";
export default {
  data() {
    return {
      allListData: [], // 所有的数据,比如这个数组存放了十万条数据
      itemHeight: 40, // 每一条(项)的高度,比如40像素
      count: 10, // 一屏展示几条数据
      start: 0, // 开始位置的索引
      end: 10, // 结束位置的索引
      topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动
      loading: false,
    };
  },
  computed: {
    // 从所有的数据allListData中截取需要展示的数据showListData
    showListData: function () {
      return this.allListData.slice(this.start, this.end);
    },
  },
  async created() {
    this.loading = true;
    this.allListData = Array.from({ length: 100000 }, (v, i) => `第 ${i + 1} 条数据`);
    this.loading = false;
  },
  methods: {
    // 滚动这里可以加上节流,减少触发频次
    handleScroll() {
      /**
       * 获取在垂直方向上,滚动条滚动了多少像素距离Element.scrollTop
       *
       * 滚动的距离除以每一项的高度,即为滚动到了多少项,当然,要取个整数
       * 例:滚动4米,一步长0.8米,滚动到第几步,4/0.8 = 第5步(取整好计算)
       *
       * 又因为我们一次要展示10项,所以知道了起始位置项,再加上结束位置项,
       * 就能得出区间了【起始位置, 起始位置 + size项数】==【起始位置, 结束位置】
       * */
      const scrollTop = this.$refs.virtualListWrap.scrollTop;
      this.start = Math.floor(scrollTop / this.itemHeight);
      this.end = this.start + this.count;
      /**
       * 动态更改定位的top值,确保联动,动态展示相应内容
       * */
      this.topVal = this.$refs.virtualListWrap.scrollTop + "px";
    },
  },
};
</script>
<style scoped lang="less">
// 虚拟列表容器盒子
.virtualListWrap {
  box-sizing: border-box;
  width: 240px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;
  .contentList {
    width: 100%;
    height: auto;
    // 搭配使用绝对定位
    position: absolute;
    top: 0;
    left: 0;
    .itemClass {
      box-sizing: border-box;
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
    }
    // 奇偶行改一个颜色
    .itemClass:nth-child(even) {
      background: #c7edcc;
    }
    .itemClass:nth-child(odd) {
      background: pink;
    }
  }
  .loadingBox {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(255, 255, 255, 0.64);
    color: green;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>

2、虚拟表格

推荐一个好用的UI组件,vxetable,看名字就知道做的是表格相关的业务。其中就包括虚拟列表。
vue2和vue3版本都支持,性能比较好,官方说:虚拟滚动(最大可以支撑 5w 列、30w 行)
强大!
官方网站地址:vxetable.cn/v3/#/table/…
这里不做过多介绍,有兴趣可以移至官网查看

本文内容转载于: 水冗水孚 - 什么?后端要一次性返回我10万条数据!且看我这8种方案机智应对!


weixin

31

评论区