海量数据渲染的解决方案

创建一个十万条数据的接口

这个自己用mock模拟或者自己用nodejs的express直接写一下就是核心代码如下

js 复制代码
route.get("/bigData", (req, res) => {
    res.header('Access-Control-A1low-origin', "*"); 
    //允许時域
    let arr=[] // 定义数组,存放十万条数据
    for(let i = 0; i < 10000; i++){ /循环添加十万条数据
        arr.push({
            id: i+1,
            name: '名字' + (i + 1),
            value: i + 1,
        })
    res.send({ code: 0, msg: "成功", data: arr } //将十万茶数据返回
})

这里贴出后端的接口 http://124.223.69.156:3300/bigData

第一种直接渲染十万条数据

js 复制代码
async load() {
    this.loading = true;
    const res = await axios.get("http://ashuai.work: 1000/bigData");
    this.arr = res.data.data;
    this.loading = false;
}

这种肯定是不行的会导致页面直接卡死

第二种使用定时器分组分批分堆依次渲染 (定时加载分堆的思想)

  1. 前端请求到十万条数据以后,先不着急渲染,先将10万条数据分堆分批次
  2. 例如一堆放10条数据,那么十万条就有一万堆
  3. 使用定时器,一次渲染一堆,渲染一万次就可以了
js 复制代码
// 分堆
for (let i=0;i < twoArr.length;i++){
    setTimeout(()=>{
        this.arr = [ ...this.arr, ...twoArr[i]]
    },20*i)
}

使用requestAnimationFrame 进行优化

解释 比如浏览器每16.7ms刷新一次,动画回调也每167ms调用一一次,这样就不会存在过度绘制的问题,动画不会掉帧,自然流畅。

js 复制代码
// 1. 对数据进行分堆处理
let twoArr = this.averageFn(res,data . data)
// 2. 定义一个泣染函数
const useTwoArr = (page)=> {
    if(page > twoArr.length){
      return
    }
    // 3. 用动画请求帧来优化
    requestAnimationFrame(()=>{
    // 4. 取出一项,拼接一项
        this.arr = [...this.arrs, ...twoArr[page]] 
       // 5. 这项进行下项
        page = page + 1
        // 6. 递归调用这个图数
        useTwoArr(page)
    })
}

表格滚动触底加载(滚动到底,再加载一堆)

js 复制代码
import elTableInfinitescroll from "el-table- infinite-scroll";
vue.use(elTableInfinitescroll);
js 复制代码
async load() {
console.1og("自动多次执行之,肖次执行会根据高度去计算要执行几次合适");
// 第1步,触底加载相当于把二维数組的每一项收出来用,用光时return停止即可
if (this.allTableData.length == 0) {
console.log("没数据啦");
return; 
}
//第二步,加载的时候,把二维数组的第一项收出来,拼接到要展示的表格数据中去
let arr = this.allTableData[0];
this.tableData = this.tableData.concat(arr);
console.log(this.tableData);
// 第三步,拼接展示以后,再把2維数组的第一项的数据删除即可
this.allTableData.shift();

使用无限加载/虚拟列表进行展示

什么是虚拟列表?

所谓的虚拟列表实际上是前端障眼法的一种表现形式。 看到的好像所有的数据都渲染了,实际上.只渲染「可视区域」的部分罢了

有点像我们看电影,我们看的话,是在块电影屏幕上,一秒秒的看(不停的放映)

但是实际上电影有俩小时,如果把两个小时的电影都铺开的话,那得需要多少块电影屏幕呢?

同理,如果10万条数据都渲染,那得需要多少dom节点元素呢?

所以我们只给用户看,他当下能看到的

如果用户要快进或快退(下拉滚动条或者上拉滚动条)

再把对应的内容呈现在电影屏幕上(呈现在可视区域内)

这样就实现了看着像是所有的dom元素每一条 数据都有渲染的障眼法效果

基本思路

  1. 写一个代表可视区域的div容器
  2. 计算可视区域可以显示的数据条数(可视区域的高度/单条数据的高度)
  3. 监听滚动,当滚动条变化时,计算被卷起的高度(scrollTop)
  4. 计算可视区域的起始下标(卷起的高度/单条数据的高度)
  5. 计算可视区域的结束下标(起始下标+可视区域可以显示的数据条数)
  6. 取起始下标到结束下标之间的数据,渲染到页面
  7. 计算起始下标的数据在整个列表中的偏移位置并设置到列表上(transform:translateY())

代码实现

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>js 实现虚拟列表</title>
  </head>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
 
    ul,
    li {
      margin: 0;
      padding: 0;
      list-style: none;
    }
 
    .box {
      width: 750px;
      height: 900px;
      overflow-y: auto;
      box-sizing: border-box;
      margin: 0 auto;
    }
 
    .content-area {
      overflow: hidden;
    }
 
    li {
      height: 100px;
      background-color: #eee;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      padding-left: 20px;
      border-radius: 12px;
      box-sizing: border-box;
    }
  </style>
  <body>
    <!-- 可视区域的高度--用户看见的高度(750px) -->
    <div class="box">
      <!-- 内容真实高度 即200条数据的高度-->
      <div class="content-area" id="contentArea">
        <ul></ul>
      </div>
    </div>
    <script>
      let el = document.querySelector(".box");
      let itemHeight = 110; //每个元素的高度(li的高度100 + marginBottom 10)
      let pageSize = Math.ceil(el.clientHeight / itemHeight); // 获取一个滚动屏最大可容纳子元素个数(向上取整)
      let data = []; //mock数据
      let startIndex = 0; //可视区第一行下标
      let endIndex = pageSize; //可视区最后一行下标
 
      // 初始化模拟数据
      let getData = () => {
        for (let i = 0; i < 200; i++) {
          data.push({
            content: `我是显示的内容${i + 1}`,
          });
        }
      };
 
      // 加载数据并插入到dom页面
      let loadData = () => {
        let html = "";
        let sliceData = data.slice(startIndex, endIndex);
        for (let i = 0; i < sliceData.length; i++) {
          html += `
              <li class="item">
                <p>${sliceData[i].content}</p>
              </li>`;
        }
        el.querySelector("#contentArea ul").innerHTML = html;
      };
 
      // 更新DOM
      let updateHtml = () => {
        let sliceData = data.slice(startIndex, endIndex);
        let itemAll = el.querySelectorAll(".item");
        for (let i = 0; i < sliceData.length; i++) {
          itemAll[i].querySelector("p").innerHTML = sliceData[i].content;
        }
      };
 
      // 滑动监听
      el.addEventListener("scroll", function () {
        let scrollTop = el.scrollTop; // 滚动高度
        startIndex = Math.ceil(scrollTop / itemHeight); // 重新计算开始的下标,div顶部卷起来的长度除以列表元素的高度
        endIndex = startIndex + pageSize;
        updateHtml(); // 重新更新dom
        el.querySelector("#contentArea ul").style.transform =
          "translateY(" + startIndex * itemHeight + "px)";
      });
 
      let init = () => {
        getData();
        loadData();
        document.getElementById("contentArea").style.height =
          itemHeight * data.length + "px"; // 占位dom的高度
      };
 
      // 页面初始化调用
      init();
    </script>
  </body>
</html>
相关推荐
王哲晓43 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v1 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
aPurpleBerry2 小时前
JS常用数组方法 reduce filter find forEach
javascript
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x2 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
我血条子呢3 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
半开半落3 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
理想不理想v4 小时前
vue经典前端面试题
前端·javascript·vue.js