给你10万条数据,如何顺滑且不卡顿不白屏的渲染出来?

96 阅读3分钟

1.延迟渲染:

在数据加载完成后,使用requestAnimationFrame来延迟渲染,这可以在下一次重绘之前进行渲染,提高性能。

定时器分页渲染会出现页面白屏掉帧现象,不推荐使用setTimeout,推荐requestAnimationFrame渲染。

出现白屏掉原因:

  • 一是setTimeout的执行时间是不确定的,它属于宏任务,需要等同步代码以及微任务执行完后执行。

  • 二是屏幕刷新频率受分辨率和屏幕尺寸影响,而setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕刷新时间相同。

requestAnimationFrame的调用频率通常为每秒60次。这意味着我们可以在每次重绘之前更新动画的状态,并确保动画流畅运行,而不会对浏览器的性能造成影响。
setInterval与setTimeout它可以让我们在指定的时间间隔内重复执行一个操作,不考虑浏览器的重绘,而是按照指定的时间间隔执行回调函数,可能会被延迟执行,从而影响动画的流畅度。

获取后端数据的公开接口可直接在浏览器打开访问:http://124.223.69.156:3300/bigData

<template>
  <el-table :data="tableData" style="width: 80%" height="480" v-loading="loading">
    <el-table-column prop="id" label="id" width="180"> </el-table-column>
    <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
    <el-table-column prop="value" label="值"> </el-table-column>
  </el-table>
</template>
<script>
  import { getDataList } from "../api/home";
  export default {
    data() {
      return {
        tableData: [],
        loading: false,
      };
    },
    methods: {
      async getData() {
        this.loading = true;
        const res = await getDataList();
        this.loading = false;
        // 1.对数据分堆处理,将10万条数据,分成10条一堆,一共1万堆
  
        const twoarr = this.avergeData(res.data.data);
        //2.定时器分页渲染函数
        for (let i = 0; i < twoarr.length; i++) {
          setTimeout(() => {
            this.tableData = [...this.tableData, ...twoarr[i]];
          }, 20 * i);
        }
      },
      avergeData(data) {
        let i = 0;
        let res = [];
        while (i < data.length) {
          res.push(data.slice(i, i + 10));
          i = i + 10;
        }
        return res;
      },
    },
    mounted() {
      this.getData();
    },
  };
</script>

优化后的代码:

<template>
  <el-table :data="tableData" style="width: 80%" height="480" v-loading="loading">
    <el-table-column prop="id" label="id" width="180"> </el-table-column>
    <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
    <el-table-column prop="value" label="值"> </el-table-column>
  </el-table>
</template>
<script>
  import { getDataList } from "../api/home";
  export default {
    data() {
      return {
        tableData: [],
        loading: false,
      };
    },
    methods: {
      async getData() {
        this.loading = true;
        const res = await getDataList();
        this.loading = false;
        // 1.对数据分堆处理,将10万条数据,分成10条一堆,一共1万堆
        const twoarr = this.avergeData(res.data.data);
        // 2.定义一个渲染函数
        const useTwoArr = (page) => {
          if (page > twoarr.length - 1) {
            return;
          }
          //3.用动画请求帧来优化
          requestAnimationFrame(() => {
            this.tableData = [...this.tableData, ...twoarr[page]];
            page = page + 1;
            //4.递归循环取出每一堆进行渲染
            useTwoArr(page);
          });
        };
        useTwoArr(0);
      },
      avergeData(data) {
        let i = 0;
        let res = [];
        while (i < data.length) {
          res.push(data.slice(i, i + 10));
          i = i + 10;
        }
        return res;
      },
    },
    mounted() {
      this.getData();
    },
  };
</script>

2.触底加载:

使用element插件el-table-infinite-scroll,安装el-table-infinite-scroll,全局引用插件

vue2 版本引用:

npm install --save el-table-infinite-scroll@2

import ElTableInfiniteScroll from "el-table-infinite-scroll"

Vue.directive("el-table-infinite-scroll", ElTableInfiniteScroll)

vue3版本引用:

npm install --save el-table-infinite-scroll

import ElTableInfiniteScroll from "el-table-infinite-scroll";

app.use(ElTableInfiniteScroll);

<template>
  <el-table
    :data="tableData"
    style="width: 80%"
    height="480"
    element-loading-text="拼命加载中"
    element-loading-spinner="el-icon-loading"
    element-loading-background="rgba(0, 0, 0, 0.8)"
    v-loading="loading"
    v-el-table-infinite-scroll="load"
  >
    <el-table-column prop="id" label="id" width="180"> </el-table-column>
    <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
    <el-table-column prop="value" label="值"> </el-table-column>
  </el-table>
</template>
<script>
  import { getDataList } from "../api/home";
  export default {
    data() {
      return {
        tableData: [],
        loading: false,
        allTableData: [],
      };
    },
    methods: {
      async getData() {
        this.loading = true;
        const res = await getDataList();
        this.loading = false;
        // 1.对数据分堆处理,将10万条数据,分成10条一堆,一共1万堆
        this.allTableData = this.avergeData(res.data.data);
        this.load();
      },
      avergeData(data) {
        let i = 0;
        let res = [];
        while (i < data.length) {
          res.push(data.slice(i, i + 10));
          i = i + 10;
        }
        return res;
      },
      load() {
        //触底加载数据
        if (this.allTableData.length === 0) {
          //没有数据啦
          return;
        }
        this.tableData = this.tableData.concat(this.allTableData[0]);
        this.allTableData.shift();
      },
    },
    mounted() {
      this.getData();
    },
  };
</script>

3.虚拟列表:

<template>

   <div ref="list" class="v-scroll" @scroll="scrollEvent($event)">
    <div class="infinite-list" :style="{ height: listHeight + 'px' }"></div>

    <div class="scroll-list" :style="{ transform: getTransform }">
      <div
        ref="items"
        class="scroll-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
      >
        {{ item.name }}
      </div>
    </div>
  </div>
  
</template>

<script>
  import { getDataList } from "../api/home";
  export default {
    data() {
      return {
        listData: listData,
        itemHeight: 60,
        //可视区域高度
        screenHeight: 600,
        //偏移量
        startOffset: 0,
        //起始索引
        start: 0,
        //结束索引
        end: null
      };
    },
    computed: {
      //列表总高度
      listHeight() {
        return this.listData.length * this.itemHeight;
      },
      //可显示的列表项数
      visibleCount() {
        return Math.ceil(this.screenHeight / this.itemHeight);
      },
      //偏移量对应的style
      getTransform() {
        return `translate3d(0,${this.startOffset}px,0)`;
      },
      //获取真实显示列表数据
      visibleData() {
        return this.listData.slice(
          this.start,
          Math.min(this.end, this.listData.length)
        );
      },
    },
    methods: {
      scrollEvent() {
        //当前滚动位置
        let scrollTop = this.$refs.list.scrollTop;
        //此时的开始索引
        this.start = Math.floor(scrollTop / this.itemHeight);
        //此时的结束索引
        this.end = this.start + this.visibleCount;
        //此时的偏移量
        this.startOffset = scrollTop - (scrollTop % this.itemHeight);
      
      },
      async getData() {
      
        const res = await getDataList();
        this.listData = res.data.data;
      
      },
   
    },
    mounted() {
      this.getData();
      this.start = 0;
      this.end = this.start + this.visibleCount;
    },
  };
</script>

<style>
  .v-scroll {
    height: 600px;
    width: 400px;
    border: 3px solid #000;
    overflow: auto;
    position: relative;
    -webkit-overflow-scrolling: touch;
    margin-left: 30px;
  }

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

  .scroll-list {
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
    text-align: center;
  }

  .scroll-item {
    padding: 10px;
    color: #555;
    box-sizing: border-box;
    border-bottom: 1px solid #999;
  }
</style>