【vue】vue高性能虚拟滚动列表【vue2和vue3版组件封装】

项目场景:

当前页显示100w多条数据,不做分页的情况进行渲染。加载和渲染页面会非常慢,滚动也非常卡顿


解决方案:

1.渲染可视窗口的列表,其他列表不进行渲染。通过修改偏移量高度进行滚动列表。
2.分段插入,同时使用window.requestAnimationFrame()函数,按帧执行你插入数据的函数
在这里插入图片描述

vue2版本

virtualList 组件包
注意:组件必传containerHeight高度(容器高度)和数据列表listData

<template>
  <div>
    <div ref="listRef" :style="{height:containerHeight}" class="listContainer" @scroll="scrollEvent($event)">
      <div class="listPhantom" :style="{ height: computedListHeight + 'px' }"></div>
      <div class="list" ref="infiniteListRef" :style="{ transform: computedGetTransform }">
        <div ref="items"
             class="listItem"
             v-for="(item,key) in computedVisibleData"
             :key="key+'-'+item?.id"
             :style="{height:'100%' }"
        ><slot :data="item" /></div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'virtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //容器高度
    containerHeight:{
      type:String,
      default:"100%"
    }
  },
  watch:{
    //监听列表数据
    listData:{
      handler() {
        //修改每一个列的高度
        this.$nextTick(()=>{
          //获取每个列表高度
          this.itemHeight = this.$refs.items ? this.$refs.items[0].offsetHeight:1
          this.end = this.start + this.computedVisibleCount;
        })
      },
      deep: true,
      immediate: true
    }
  },
  computed:{
    //获取真实显示列表数据
    computedVisibleData(){
      return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
    },
    //列表总高度
    computedListHeight(){
      return BigInt(this.listData.length * this.itemHeight);
    },
    //可显示的列表项数
    computedVisibleCount(){
      return Math.ceil(this.screenHeight / this.itemHeight)
    },
    //偏移量对应的style
    computedGetTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    }
  },
  data() {
    return {
      //每列高度
      itemHeight:1,
      //可视区域高度
      screenHeight:0,
      //偏移量
      startOffset:0,
      //起始索引
      start:0,
      //结束索引
      end:null,
    };
  },
  methods: {
    scrollEvent() {
      //当前滚动位置
      let scrollTop = this.$refs.listRef.scrollTop;
      //此时的开始索引
      this.start = Math.floor(scrollTop / this.itemHeight);
      //此时的结束索引
      this.end = this.start + this.computedVisibleCount;
      //此时的偏移量
      this.startOffset = scrollTop - (scrollTop % this.itemHeight);

    },
    //页面初始化
    handleInit(){
      this.screenHeight = this.$el.clientHeight;//客户端高度
      this.start = 0;//列表开始索引
      this.end = this.start + this.computedVisibleCount;//列表结束索引
    }
  },
  mounted() {
    this.handleInit()
  },
}
</script>

<style scoped lang="less">
.listContainer {
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

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

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

.listItem {
  padding: 10px;
  color: #555;
  box-sizing: border-box;
}
</style>

app.vue文件
分段插入数据

<template>
  <div id="app">
    <div style="background-color: yellow">
      <VirtualList ref="testRef" :containerHeight="containerHeight" :listData="rows" >
        <template slot-scope="row">
          <div style="border-bottom: 1px solid red;">
            {{ row.data.id }}
          </div>
        </template>
      </VirtualList>
    </div>
  </div>
</template>
<script>
import VirtualList from './components/virtualList.vue';

export default {
  data(){
    return {
      once:0,//每次插入的数量
      containerHeight:0,
      total:0,//总条数
      countRender:0,//已经渲染次数
      loopCount:0,//需要插入的次数
      rows:[]
    }
  },
  components:{VirtualList},
  created() {
    this.containerHeight = 300 +'px';
  },
  mounted() {
    this.handleInit();
  },
  methods:{
    handleInit(){
      setTimeout(()=>{
        // 百万条数据
        this.total = 10000000000;
        // 单次插入 可自定义
        this.once = 20;
        // 需要插入的次数 向上取整
        this.loopCount = Math.ceil(this.total / this.once);
        // 当前渲染次数
        this.countRender = 0;
        this.handleRender();
      },500)
    },
    //百万数据分段插入
    handleRender(){
      for (let i = 0; i < this.once; i++) {
        this.rows.push({id:this.countRender+'-'+i})
      }
      // 渲染次数加1,控制递归的次数
      this.countRender++;

      if (this.countRender < this.loopCount) {
        window.requestAnimationFrame(this.handleRender);
      }
    }
  }
}
</script>
<style>
#app {
  height: 500px;
  width: 100%;
}
</style>

vue3版本

virtualList 组件包
注意:组件必传data高度(容器高度)和数据列表containerHeight

<!--
*- coding = utf-8 -*-
#@Time : 2023-06-29 23:59
#@Author : 管茂良
#@File : virtualList.vue
#@web  : www.php-china.com
#@Software: WebStorm
-->
<template>
  <div>
    <div class="listContainer" :style="{height:props.containerHeight}" @scroll="handleScroll">
      <div class="listContainerPhantom" :style="{ height: listHeight + 'px' }"></div>
      <div class="listContainerList" :style="{ transform: computedTransform }">
        <div ref="listItemRef"  :key="index+'-'+item?.id" v-for="(item,index) in computedVisibleData">
          <slot :data="item"></slot>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {computed, defineProps, nextTick, onMounted, ref, watch, withDefaults} from "vue";

let startNum = ref(0)
let endNum = ref(0)

let startOffset = ref(0);//偏移量
let listHeight = ref(0);//列表总高度
let listItemHeight = ref(0);//每项高度
let listItemRef = ref(null);

interface propsInterface{
  data?:any              //列表数据
  containerHeight?:string   //容器高度
}
let props = withDefaults(defineProps<propsInterface>(),{
  //所有数据
  data:[],
  containerHeight:"",
})

const computedTransform = computed(()=>{
  return `translate3d(0,${startOffset.value}px,0)`;
})
//可看得见的数据
const computedVisibleData = computed(()=>{
  return props.data.slice(startNum.value,Math.min(endNum.value,props.data.length))
})


const handleScroll = ()=>{
  let listContainer = document.querySelector(".listContainer");//列表容器
  let listContainerHeight = listContainer?.offsetHeight

  //可显示的数量
  let showNum = Math.ceil(listContainerHeight / listItemHeight.value)

  //滚动高度
  let contentScrollTop = Math.floor(listContainer.scrollTop);

  let curNum = Math.ceil(contentScrollTop / listItemHeight.value);
  startNum.value = curNum
  endNum.value = showNum + startNum.value

  //滚动高度减去空隙
  startOffset.value = contentScrollTop - (contentScrollTop%showNum)

}

const handleInit = ()=>{
  let listContainer = document.querySelector(".listContainer");//列表容器
  let listItem = listItemRef.value ;//每一列

  let listContainerHeight = listContainer?.offsetHeight
  listItemHeight.value = listItem ? listItem[0]?.offsetHeight : 1

  //列表总高度
  listHeight.value = props.data.length*listItemHeight.value

  startNum.value = 0
  let showNum = Math.ceil(listContainerHeight / listItemHeight.value)
  endNum.value = startNum.value + showNum
}
//监听列表高度,有变化,重新初始化
watch(()=>props.data,(value)=>{
  handleInit()
},{immediate:true,deep:true})

onMounted(()=>{
  handleInit()
})
</script>

<style scoped lang="less">
.listContainer{
  height:400px;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
  .listContainerPhantom{
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
  }
  .listContainerList{
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
    text-align: center;
  }

}
</style>

app.vue文件
分段插入数据

<template>
  <div class="app-container">
    <VirtualList :data="data" :containerHeight="virtualListHeight" >
      <template #default="row">
        <div class="listItem u-f u-f-ac u-f-spa" >
          {{row.data.id}}
        </div>
      </template>
    </VirtualList>
  </div>
</template>

<script setup lang="ts">
import {defineAsyncComponent, onMounted, ref} from "vue";
const VirtualList = defineAsyncComponent(()=>import("@/components/virtualList/index"))

let once = ref(0)//每次插入的数量
let total = ref(0)//总条数
let countRender = ref(0)//已经渲染次数
let loopCount = ref(0)//需要插入的次数
let data = ref([])
let virtualListHeight = ref("")


const handleInit = () => {
  data.value.length = 0;
  setTimeout(()=>{
    // 百万条数据
    total.value = 10000;
    // 单次插入 可自定义
    once.value = 20;
    // 需要插入的次数 向上取整
    loopCount.value = Math.ceil(total.value / once.value);
    // 当前渲染次数
    countRender.value = 0;
    handleRender();
  },500)
}
//百万数据分段插入
const handleRender = () => {
  for (let i = 0; i < once.value; i++) {
    data.value.push({id:countRender.value+'-'+i})
  }
  // 渲染次数加1,控制递归的次数
  countRender.value++;

  if (countRender.value < loopCount.value) {
    window.requestAnimationFrame(handleRender);
  }
}
onMounted(()=>{
  virtualListHeight.value = (document.documentElement.offsetHeight-400)+"px"
  handleInit()
})
</script>

<style scoped lang="less">
.app-container{
  background-color: gray;
  height:500px;
  width: 500px;
}
.listItem{
  border-bottom:1px solid red;
  padding:10px;
}
</style>

最终效果:
在这里插入图片描述
在这里插入图片描述

案例源码

https://gitee.com/derekgo/tool-collection/tree/dev/vue/virtualList

踩坑不易,还希望各位大佬支持一下 \textcolor{gray}{踩坑不易,还希望各位大佬支持一下} 踩坑不易,还希望各位大佬支持一下

📃 个人主页: \textcolor{green}{个人主页:} 个人主页: 沉默小管

📃 个人网站: \textcolor{green}{个人网站:} 个人网站: 沉默小管

📃 个人导航网站: \textcolor{green}{个人导航网站:} 个人导航网站: 沉默小管导航网

📃 我的开源项目: \textcolor{green}{我的开源项目:} 我的开源项目: vueCms.cn

🔥 技术交流 Q Q 群: 837051545 \textcolor{green}{技术交流QQ群:837051545} 技术交流QQ群:837051545

👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

如果有不懂可以留言,我看到了应该会回复
如有错误,请多多指教

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/33510.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL—SQL优化详解(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

Flink入门学习(一)

Flink 1. 概述 分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架&#xff0c;用于对无界和有界数据流进行有状态计算。Flink 被设计在所有常见的集群环境中运行&#xff0c;以内存执行速度和任意规模来执行计算。 有界流&#xff1a;有定义流的开始&am…

Springboot 集成Prometheus 数据采集 使用grafana 监控报告告警 邮件配置

目录 Springboot 相关 Pom 重点包 如果有需要可以增加安全包-一般内部机房没啥事-&#xff08;非必选&#xff09; Application.yml配置文件-&#xff08;非必选&#xff09; Application.properties management.endpoints.web.exposure.include介绍 启动类 查看监控信…

用于语义图像分割的弱监督和半监督学习:弱监督期望最大化方法

这时一篇2015年的论文&#xff0c;但是他却是最早提出在语义分割中使用弱监督和半监督的方法&#xff0c;SAM的火爆证明了弱监督和半监督的学习方法也可以用在分割上。 这篇论文只有图像级标签或边界框标签作为弱/半监督学习的输入。使用期望最大化(EM)方法&#xff0c;用于弱…

【Solr】中文分词配置

提示&#xff1a;在设置中文分词前需确保已经生成过core&#xff0c;未生成core的可以使用&#xff1a;solr create -c "自定义名称"进行定义。 未分词前的效果预览&#xff1a; 下载分词器&#xff1a; 下载地址: https://mvnrepository.com/artifact/com.github.m…

Spring Cloud 之注册中心 Eureka 精讲

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

nginx配置开机启动(Windows环境)

文章目录 1、下载nginx&#xff0c;并解压2、配置nginx.conf&#xff0c;并启动Nginx3、开机自启动 1、下载nginx&#xff0c;并解压 2、配置nginx.conf&#xff0c;并启动Nginx 两种方法&#xff1a; 方法一&#xff1a;直接双击nginx.exe&#xff0c;双击后一个黑色弹窗一闪…

ELK日志收集系统集群实验

ELK日志收集系统集群实验 目录 一、实验拓扑 二、环境配置 三、 安装node1与node2节点的elasticsearch 1. 安装 2.配置 3.启动elasticsearch服务 4.查看节点信息 四、在node1安装elasticsearch-head插件 1.安装node 2.拷贝命令 3.安装elasticsearch-head 4.修改el…

【机器学习】十大算法之一 “PCA”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

【夜深人静学数据结构与算法 | 第十一篇】枚举算法

目录 前言&#xff1a; 枚举算法&#xff1a; 优点&#xff1a; 枚举算法的种类&#xff1a; 枚举算法案例&#xff1a; 343. 整数拆分 - 力扣&#xff08;LeetCode&#xff09; 12. 整数转罗马数字 - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&…

【手撕算法|动态规划系列No.1】leetcode1137. 第 N 个泰波那契数

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

exe的python文件打包

【步骤01】 【在命令行中用pip工具安装Pyinstaller模块】 pip install Pyinstaller 步骤02】 【切换命令行的路径到你要打包的Python源文件的文件夹路径下】 【下面是我要打包的Python源文件&#xff08;散点坐标图.py&#xff09;及其文件夹路径】 【步骤03】 【执行Pyi…

使用SSH远程直连Docker容器

文章目录 1. 下载docker镜像2. 安装ssh服务3. 本地局域网测试4. 安装cpolar5. 配置公网访问地址6. SSH公网远程连接测试7.固定连接公网地址8. SSH固定地址连接测试 转载自cpolar极点云文章&#xff1a;SSH远程直连Docker容器 在某些特殊需求下,我们想ssh直接远程连接docker 容器…

SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)

文章目录 1. 环境准备1. 查询全部2. 根据 name 查询 match 分词查询3. 根据 name 和 品牌查询 multiMatch 分词查询4. 根据 brand 查询 match 分词查询5. 按照价格 范围查询6. 精确查询7. boolQuery8. 分页9. 高亮查询9. 公共解析 上一节讲述了 SpringBoot 实现 elasticsearch …

【图像处理OpenCV(C++版)】——5.3 图像平滑之均值平滑(滤波)

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

Linux终端与进程的关系 ( 1 ) -【Linux通信架构系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the…

C高级重点

1、请简要描述一下Linux文件系统的层级结构&#xff0c;包括不同目录的作用和功能。 Linux的文件系统结构是一个倒插树结构&#xff0c;所有的文件都从根目录出发。 2、find指令的用途 find 查找的路径 -name 文件名 ----->在指定路径下&#xff0c;以文件名为条件查找文…

总结vue3 的一些知识点:​Vue3 起步

目录 引言 Vue3 混入 实例 选项合并 实例 实例 全局混入 实例 Vue3 起步 Vue 3.0 实例 data 选项 实例 方法 总结 引言 Vue 进阶系列教程将在本号持续发布&#xff0c;一起查漏补缺学个痛快&#xff01;若您有遇到其它相关问题&#xff0c;非常欢迎在评论中留言讨…

Ubuntu 20.04.02 LTS安装virtualbox7.0

ubuntu22.04的软件仓库也有virtualbox&#xff0c;不过版本较老。 使用安装命令&#xff1a;sudo apt install virtualbox 如果想要安装最新版&#xff0c;那么需要去官网下载deb包或者使用官方的仓库。 这里采用安装Oracle官方仓库的方法。 执行如下命令&#xff1a; wge…

HTTP调用:你考虑到超时、重试、并发了吗?

今天&#xff0c;我们一起聊聊进行 HTTP 调用需要注意的超时、重试、并发等问题。 与执行本地方法不同&#xff0c;进行 HTTP 调用本质上是通过 HTTP 协议进行一次网络请求。网络请求必然有超时的可能性&#xff0c;因此我们必须考虑到这三点&#xff1a; 首先&#xff0c;框架…