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

news2024/12/24 0:11:39

项目场景:

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


解决方案:

之渲染可视窗口的列表,其他列表不进行渲染。通过修改偏移量高度进行滚动列表。
在这里插入图片描述

vue2版本

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

<template>
  <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: getTransform }">
      <div ref="items"
           class="list-item"
           v-for="item in visibleData"
           :key="item.id"
           :style="{height:'100%' }"
      ><slot :data="item" /></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'virtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //容器高度
    containerHeight:{
      type:String,
      default:"100%"
    }
  },
  watch:{
    //监听列表数据
    listData:{
      handler() {
        this.visibleData = this.listData.slice(this.start, Math.min(this.end,this.listData.length));
      },
      deep: true,
      immediate: true
    },
    //监听可视化数据,修改每一个列的高度
    visibleData:{
      handler() {
        this.$nextTick(()=>{
          this.itemHeight = this.$refs.infiniteListRef.offsetHeight
        })
      }
    }
  },
  computed:{
    //列表总高度
    computedListHeight(){
      return this.listData.length * this.itemHeight;
    },
    //可显示的列表项数
    computedVisibleCount(){
      return Math.ceil(this.screenHeight / this.itemHeight)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    }
  },
  data() {
    return {
      //可见数据
      visibleData:[],
      //每列高度
      itemHeight:200,
      //可视区域高度
      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;
}

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

应用virtualList

<template>
...
	<VirtualList :containerHeight="containerHeight" :listData="rows" >
        <template slot-scope="row">
         	{{row.data}}
        </template>
     </VirtualList>
...
</template>
<script>
import VirtualList from "./virtualList.vue";
export default{
  data(){
	return(){
		containerHeight:"",//自己设置虚拟滚动列表容器高度
	}
  },
  created() {
    this.containerHeight = document.documentElement.offsetHeight+'px'
  },
}
</script>

vue3版本

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

<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 }">
                <template v-for="(item,index) in computedVisibleData">
                    <slot :data="item"></slot>
                </template>
            </div>
        </div>
    </div>
</template>

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

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

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

    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 = document.querySelector(".listItem");//每一列

        let listContainerHeight = listContainer?.offsetHeight
        listItemHeight.value = listItem?.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(()=>listItemHeight.value,(value)=>{
      handleInit()
    },{immediate:true,deep:true})

    onMounted(()=>{
        handleInit()
        nextTick(()=>{
          listItemHeight.value = document.querySelector(".listItem").offsetHeight
        })
    })
</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>

应用virtualList

<template>
...
	 <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>
...
</template>
<script setup lang="ts">
const VirtualList = defineAsyncComponent(()=>import("./virtualList.vue"))
let data = ref([
  {
    id:1,
  },
  {
    id:2,
  },
  {
    id:3,
  },
  {
    id:4,
  },
  {
    id:5,
  },
  {
    id:6,
  },
  {
    id:7,
  },
  {
    id:8,
  },
  {
    id:9,
  },
  {
    id:10,
  },
  {
    id:11,
  },
  {
    id:12,
  },
  {
    id:13,
  },
  {
    id:14,
  },
  {
    id:15,
  }
])
let virtualListHeight = ref("")

onMounted(()=>{
  virtualListHeight.value = (document.documentElement.offsetHeight-500)+"px"
})
</script>

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

踩坑不易,还希望各位大佬支持一下 \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}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s安装环境准备:Virtualbox安装CentOS;复制多个CentOS虚拟机

1.安装virtualbox 下载virtualbox https://www.virtualbox.org/wiki/Downloads 安装&#xff08;windows&#xff09; 双击VirtualBox-7.0.8-156879-Win.exe 选择安装目录 安装完成后&#xff0c;打开virtualbox 2.下载CentOS 下载CentOS-7-x86_64-DVD-2009.iso http://isoredi…

做项目去实习到底做的什么?

300万字&#xff01;全网最全大数据学习面试社区等你来&#xff01; 今天是手机编辑的文章&#xff0c;说说做项目/实习这回事。 我之前发过一些视频&#xff0c;讲校招四要素的&#xff0c;其中一个很重要的部分就是实习。 对社招同学来说&#xff0c;就简单了&#xff0c;面试…

解决磁盘占用率过高100%问题

方法一&#xff1a;关闭程序 首先打开任务管理器&#xff0c;单击磁盘占用率一栏进行排序&#xff0c;查看占用磁盘最高的应用。若占用率最高的始终是同一个三方程序&#xff0c;可尝试卸载。 注&#xff1a;开机时由于频繁读写磁盘&#xff0c;磁盘占用率会很高&#xff0c;等…

vue内嵌原生前端三件套(html+CSS+JavaScript)

问题 vue内嵌原生前端三件套&#xff08;htmlCSSJavaScript&#xff09;&#xff0c;运行后前端页面无响应 详细问题 笔者使用vue框架进行开发&#xff0c; 对于可视化大屏采用echarts实现&#xff0c;但是网上所提供的echarts可视化大屏模板多采用原生前端三件套&#xff0…

13 GDI绘图技术

文章目录 GDI技术GDI 对象画笔对象画刷对象位图对像创建一个位图字体对象 区域对象区域组合 GDI技术 GDI(graphics Device Interface):图形设备接口&#xff0c;用于绘图。 In Windows CE, as in Windows-based desktop platforms, the graphics device interface (GDI) contr…

EasyExcel 的简单使用(读取写入)

文章目录 前言一、创建项目二、核心代码2.1 org.feng.bean包中的类2.1.1 Sex类2.1.2 User类 2.2 org.feng.constant包中的类2.2.1 Constant类 2.3 org.feng.converter包中的类2.3.1 ListDataConverter类2.3.2 SexConverter类 2.4 org.feng.listener包中的类2.4.1 UserReadListe…

android用java生成crc校验位

在串口通信中&#xff0c;经常会用到后两位生成crc校验位的情况。 下面是校验位生成方法&#xff1a; public static String getCRC(String data) {data data.replace(" ", "");int len data.length();if (!(len % 2 0)) {return "0000";}in…

Springboot集成mybatisplus的问题处理

文章目录 前言一个接口多个实现解决方案 Invalid bound statement (not found)解决方案 总结 前言 新接触mybatisPlus的小伙伴可能会遇到各种各样的问题&#xff0c;尤其是mybatis的xml文件及类的注入问题&#xff0c;下面我们就看一下常见的问题吧。 一个接口多个实现 报错…

python numpy 多维数据广播

广播规则&#xff1a;从最右侧开始广播。 Broadcasting — NumPy v1.25 Manual 截图 下面给出一些样例&#xff1a; 三维矩阵广播 a np.array([[[0,0],[0,0]],[[0, 0],[0, 0]]])print(-*10, a, -*10) print(a.shape) print(a)b np.array([[[1]],[[2]]]) print(-*10, b, -*…

AIGC 3D引擎-LayaAir3.0正式版发布了

2016年6月30日&#xff0c;LayaAir引擎1.0正式版首次发布&#xff0c;今天迎来了它的7周岁生日。 7年&#xff0c;3个大版本&#xff0c;代表着引擎不同阶段、不同的时代、不同的定位。 2016年6月的第1代引擎版本定位是极致性能&#xff0c;支持2D与3D游戏开发&#xff0c; 满足…

【C/C++实现进程间通信 三】管道通信机制

文章目录 前情回顾思路源码Publisher.cppSubscriber.cpp 效果 前情回顾 上一期已经讲解过了进程的相关概念以及进程间通信的实现原理&#xff0c;下面仅展示管道通信机制实现进程间通信的相关代码。 思路 /*本项目主要用于以管道通信的方式进行进程间通信的测试。1.主要包含…

Java面试Day17

1.什么是 Java 内部类&#xff1f; 内部类的分类有哪些 &#xff1f;内部类有哪些优点和应用场景&#xff1f; 顾名思义&#xff0c;内部类是指定义在某一个类中的类&#xff0c;主要分为成员内部类&#xff0c;静态内部类&#xff0c;局部内部类和匿名内部类四种。 创建与获取…

(五)python实战——使用sqlalchemy完成Sqlite3数据库表的增、删、查、改操作案例

前言 本节内容我们使用sqlalchemy框架完成Sqlite3数据库表的增删查改等常规操作&#xff0c;相较于原生Sqlite的数据库操作&#xff0c;sqlalchemy通过ORM映射完成实体对象的映射&#xff0c;通过映射关系完成对象和数据的转换&#xff0c;完成数据的操作。 正文 ①在项目中…

基于Tars高并发IM系统的设计与实现-基础篇

基于Tars高并发IM系统的设计与实现–基础篇 作者简介 兰怀玉 毕业于中央民族大学计算机专业 先后供职国内外多家公司软件研发设计岗位&#xff0c;有丰富的软件研发经验。 从事IM领域设计研发十余年&#xff0c;先后领衔多个IM通讯系统设计与研发发&#xff0c;拥有丰富的IM系…

算法:哲学家就餐问题

问题描述 由Dijkstra提出并解决的哲学家就餐问题是典型的同步问题。该问题描述的是五个哲学家共用一张圆桌&#xff0c;分别坐在周围的五张椅子上&#xff0c;在圆桌上有五个碗和五只筷子&#xff0c;他们的生活方式是交替的进行思考和进餐。平时&#xff0c;一个哲学家进行思考…

大语言模型微调和PEFT高效微调

目录标题 1 解释说明1.1 预训练阶段1.2 微调阶段2 几种微调算法2.1 在线微调2.2 高效微调2.2.1 RLHF2.2.2 LoRA2.2.3 Prefix Tuning2.2.4 Prompt Tuning2.2.5 P-Tuning v21 解释说明 预训练语言模型的成功,证明了我们可以从海量的无标注文本中学到潜在的语义信息,而无需为每一…

信号链噪声分析11

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 如今的射频(RF)系统变得越来越复杂。高度的复杂性要求所有系统指标&#xff08;例如严格的 链接和噪声预算&#xff09;达到最佳性能。确保整个信号链的正确设计至关重要。而信…

深入乳腺癌谜团:无监督学习与R语言的勘探之旅

一、引言 乳腺癌作为全球常见的恶性肿瘤&#xff0c;给患者和医学界带来了巨大的挑战。据世界卫生组织的数据显示&#xff0c;乳腺癌是妇女中最常见的癌症之一&#xff0c;并且是全球癌症相关死亡的主要原因之一[1]。因此&#xff0c;研究乳腺癌&#xff0c;并努力提高其早期检…

1085会议桌牌

机种名 蓝牙会议桌牌 型号 PE1085R_D_BLE 外观尺寸 280x58x129.9mm 可视区域 258.7690.68mm 外观颜色 银色 工作电源 3.7V锂电池供电&#xff0c;Type C充电口 显示技术 E-INK电子纸&#xff0c;双屏 像素 1360x480 像素颜色 黑/白/红 视角 约180 适用温度 …

【Java】直接return 会触发try-catch 里面的finally的方法么

&#x1f431;‍&#x1f680;/背景 try-catch 主要的作用是捕获异常&#xff0c;那么程序没有异常&#xff0c;finally里面代码能否执行&#xff1f; 特别是如果我们前面进行了加锁等操作&#xff0c;没有释放锁&#xff0c;那不是会造成业务逻辑问题, 先说结论&#xff1a;…