「前端进阶」高性能渲染十万条数据(虚拟列表) (自己修改版本)

前言

在工作中,有时会遇到需要一些不能使用分页方式来加载列表数据的业务情况,对于此,我们称这种列表叫做长列表。比如,在一些外汇交易系统中,前端会实时的展示用户的持仓情况(收益、亏损、手数等),此时对于用户的持仓列表一般是不能分页的。
在高性能渲染十万条数据(时间分片)一文中,提到了可以使用时间分片的方式来对长列表进行渲染,但这种方式更适用于列表项的DOM结构十分简单的情况。本文会介绍使用虚拟列表的方式,来同时加载大量数据。

为什么需要使用虚拟列表

在实际的工作中,列表项必然不会仅仅只由一个li标签组成,必然是由复杂DOM节点组成的。

那么可以想象的是,当列表项数过多并且列表项结构复杂的时候,同时渲染时,会在Recalculate Style和Layout阶段消耗大量的时间。

而虚拟列表就是解决这一问题的一种实现。

什么是虚拟列表

虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
假设有1万条记录需要同时渲染,我们屏幕的可见区域的高度为500px,而列表项的高度为50px,则此时我们在屏幕中最多只能看到10个列表项,那么在首次渲染的时候,我们只需加载10条即可。

说完首次加载,再分析一下当滚动发生时,我们可以通过计算当前滚动值得知此时在屏幕可见区域应该显示的列表项。

假设滚动发生,滚动条距顶部的位置为150px,则我们可得知在可见区域内的列表项为第4项至`第13项。

实现

虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。

计算当前可视区域起始数据索引(startIndex)
计算当前可视区域结束数据索引(endIndex)
计算当前可视区域的数据,并渲染到页面中
计算startIndex对应的数据在整个列表中的偏移位置startOffset并设置到列表上

由于只是对可视区域内的列表项进行渲染,所以为了保持列表容器的高度并可正常的触发滚动

下面是vue代码
app.vue

<template>
    <div id="app">
  <div class="VirtualList" :style="{height: windowH +'px', width:windowW+'px'}">
       <VirtualList :listData="data" :itemSize="60"/>
  </div>
  </div>
</template>

<script>
import VirtualList from "./components/VirtualList.vue";
let d = [];
for (let i = 0; i < 100000; i++) {
  d.push({ id: i, value: i });
}
export default {
    name: 'app',
    data() {
        return {
          data: d

        };
      },
      computed:{
          windowW(){
              return document.documentElement.clientWidth
          },
          windowH(){
               return document.documentElement.clientHeight
          }
      },
    components: {
        VirtualList
    }
};
</script>

<style>
    .VirtualList{
        display: flex;
        justify-content: center;
        width: 80%;
        height: 90%;
    }
#app {

    height: 100%;
    width: 100%;
}
</style>

VirtualList.vue

<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
      <span>test   {{screenHeight}}</span>
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div ref="items"
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
      >{{ item.value }}</div>
    </div>
  </div>
</template>

<script>
export default {
  name:'VirtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //每项高度
    itemSize: {
      type: Number,
      default:200
    }
  },
  computed:{
    //列表总高度
    listHeight(){
      return this.listData.length * this.itemSize;
    },
    //可显示的列表项数
    visibleCount(){
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取真实显示列表数据
    visibleData(){
      return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
    }
  },
  mounted() {
    this.screenHeight = this.$el.clientHeight;
    this.start = 0;
    this.end = this.start + this.visibleCount;
    console.log("listData",this.listData)
    console.log("listHeight",this.listHeight)
  },
  data() {
    return {
      //可视区域高度
      screenHeight:0,
      //偏移量
      startOffset:0,
      //起始索引
      start:0,
      //结束索引
      end:null,
    };
  },
  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;
  -webkit-overflow-scrolling: touch;
}

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

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

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

原文地址:https://www.cnblogs.com/caominjie/p/11802501.html

时间: 2024-08-03 08:09:54

「前端进阶」高性能渲染十万条数据(虚拟列表) (自己修改版本)的相关文章

「前端进阶」高性能渲染十万条数据(时间分片)

前言在实际工作中,我们很少会遇到一次性需要向页面中插入大量数据的情况,但是为了丰富我们的知识体系,我们有必要了解并清楚当遇到大量数据时,如何才能在不卡主页面的情况下渲染数据,以及其中背后的原理. 对于一次性插入大量数据的情况,一般有两种做法: 时间分片虚拟列表本文作为开篇,着重来介绍如何使用时间分片的方式来渲染大量数据,虚拟列表相关的内容,日后会持续整理. 最粗暴的做法(一次性渲染)我们先来看看最粗暴的做法,一次性将大量数据插入到页面中: <ul id="container"&g

使用Python对MySQL数据库插入二十万条数据

1.当我们测试的时候需要大量的数据的时候,往往需要我们自己造数据,一条一条的加是不现实的,这时候就需要使用脚本来批量生成数据了. import pymysql import random import string # 建立数据库连接 mysql = pymysql.connect(host="数据库IP", user="数据库用户名",port=3306,password="数据库密码", charset='utf8', autocommit=

【转载】从0开始学习 GitHub 系列之「Git 进阶」

转载自http://stormzhang.com 关于 Git 相信大家看了之前一系列的文章已经初步会使用了, 但是关于Git还有很多知识与技巧是你不知道的,今天就来给大家介绍下一些 Git 进阶的知识. 1. 用户名和邮箱 我们知道我们进行的每一次commit都会产生一条log,这条log标记了提交人的姓名与邮箱,以便其他人方便的查看与联系提交人,所以我们在进行提交代码的第一步就是要设置自己的用户名与邮箱.执行以下代码: git config --global user.name "storm

前端进阶——浏览器页面渲染过程

一 .构建 DOM 和 CSSOM 树 浏览器渲染页面前需要先构建 DOM 和 CSSOM 树. 浏览器解析过程大概经过:字节 → 字符 → 令牌 → 节点 → 对象模型. 浏览器处理html页面的方式如下图: 1.转换:浏览器从磁盘或网络读取HTML的原始字节,并根据文件指定的编码将它们转换成各个字符 2.令牌化:浏览器将字符串转换成W3C HTML5标准规定的各种令牌(节点) 3.词法分析:发出的令牌转换成定义其属性和规则的对象 4.DOM构建:根据标记之间的关系构建dom 整个流程的最终输

GitHub 系列之「Git 进阶」

1.用户名和邮箱 我们知道我们进行的每一次 commit 都会产生一条 log,这条 log 标记了提交人的姓名与邮箱,以便其他人方便的查看与联系提交人,所以我们在进行提交代码的第一步就是要设置自己的用户名与邮箱.执行以下代码: git config --global user.name "stormzhang" git config --global user.email "[email protected]" 以上进行了全局配置,当然有些时候我们的某一个项目想要

「前端词典」这些功能其实不需要 JS,CSS 就能搞定

直接入题 每个单词的首字母大写 其实我第一次看到这个功能的时候就是使用 JS 去实现这个功能,想都没想 CSS 可以完成这个功能.马上就屁颠屁颠的写了一个方法: function capitalizeFirst( str ) {let result = '';result = str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());return result} 写完这个方法后,还有点小得意,也就没想其他方案.直到有一天看到

进博会启幕 | 这场全球企业的「美丽机遇」,SAP准备好了!

编者按中国国际进口博览会的召开在体现中国开放.共赢的态度的同时,也为全球企业开创了更多在中国市场的「美丽机遇」.德国是首届中国国际进口博览会主宾国之一.在各行各业,都涌现出了许许多多德国企业缔造的行业神话.以SAP为代表的德国企业,在本次进博会中,吸引了众多媒体聚焦. △以上图片来自<新华网>.<光明日报>.<文汇报>等媒体的报道 首届中国国际进口博览会将于11月5日-10日在上海举行.作为全球首个以进口为主题的博览会,中国国际进口博览会受到广泛关注和期待. 进口博览会

百度地图Canvas实现十万CAD数据秒级加载

背景 前段时间工作室接到一个与地图相关的项目,我作为项目组成员主要负责地图方面的设计和开发.由于地图部分主要涉及的是前端页面的显示,作为一名Java后端的小白,第一次写了这么多HTML和JavaScript. 项目大概是需要将一张CAD的图(导出大概三十万条数据)叠加在地图上,在接Canvas之前考虑了很多种方案,最后都否定了.首先我们想利用百度地图原生的JavaScript API实现线和点的加载,但是经过测试,当数据达到2000左右,加载时间就已经达到了数十秒,后来直接测试了一万条数据,浏览

遭遇AutoMapper性能问题:映射200条数据比100条慢了近千倍

今天遇到了AutoMapper的一个性能问题,使用的是AutoMapper的Project特性,AutoMapper版本是3.3.0,代码如下: return await _repository .GetByStartId(startIngId, itemCount) .Project() .To<TDto>() .ToListAsync(); 当获取包含200条数据的列表时,竟然超过5秒. GetDocs(3000, 200) 6304ms GetDocs(3000, 200) 5822ms