vue性能优化之虚拟列表滚动

在这里插入图片描述

一、前言

前端的性能瓶颈那就是页面的卡顿,当然这种页面的卡顿包含了多种原因。
例如HTTP请求过多导致数据加载变慢,下载的静态文件非常大导致页面加载时间很长,js中一些算法响应的时间过长等。很多前端工程师都花费很多的精力在dom渲染上来优化页面加载。

二、浏览器渲染瓶颈

首先大家要明重绘回流(重排)的概念:

  • 重绘(repaint):当Render Tree 中的一些元素需要更新元素本身的属性,只影响外观样式和颜色等,不影响整个布局。

  • 回流(reflow):当Render Tree 中的某些元素因为规模、尺寸、位置等改变时,会影响整个布局。

回流必定发生重绘,重绘不一定发生回流
所以大家可以知道,回流所造成的影响是比较大的,如果页面中频繁的触发回流的操作,那么最终造成页面卡顿也是肯定的。

造成回流和重绘的操作有以下类别:

  • 页面初始化
  • 添加或者删除页面上的可视区DOM元素
  • 元素位置发生改变,定位和浮动,盒模型
  • 页面文本内容发生变化,影响输入框的大小改变。
  • 图片显示加载,如果没有加载图片又会被替换成相应提示文字信息。
  • 浏览器窗口尺寸大小变化(回流是根据视口大小来计算页面元素的位置和大小)

其实对于这些需要考虑的因素,一些浏览器也是做出了相应的处理,因为每次回流可能会造成巨大的影响,浏览器本身会实现一个队列记录每次回流时操作,当存放的操作数量达到一定值或者达到一定时间后会对队列中的操作进行清空,并一次性进行一次回流,让多次回流操作压缩成一次回流操作执行,提高效率。

浏览器的瓶颈主要在于:

  • 无法一次性渲染太多的DOM元素。
  • 每次滚动事件将会让对应的DOM中所有元素重新渲染。

针对于浏览器的瓶颈问题,有三种解决办法:数据分页、无限滚动、虚拟滚动

三、数据分页

许多网页和应用程序都会用到这样的方,对需要展示的大量数据进行分割分页,后端已经做好了分页,前端只需要调用后端的接口传入相应的第几页的参数就能获取到,减少了一次性需要渲染的行数,但是如果查询的表列数非常多,还是可能会渲染很多元素,不是一个很稳定的方法。

四、无限滚动

优点很明显,不需要一次将数据请求完,当用户下拉到底部时,才使用ajax动态从服务器拉取接下来的数据。但是这又导致了一个问题,如果用户疯狂进行下拉呢,这就会导致浏览器创建多个多余的节点,出现冗余,并且你拥有多少个节点,vue就会diff多少个节点,这样的场景会带来多余的性能消耗和内存占用。

五、虚拟滚动

虚拟滚动其实就是综合数据分页和无限滚动的方法,在有限的视口中只渲染我们所能看到的数据,超出视口之外的数据就不进行渲染,可以通过计算可视范围内的但单元格,保证每一次滚动渲染的DOM元素都是可以控制的,不会担心像数据分页一样一次性渲染过多,也不会发生像无限滚动方案那样会存在数据堆积,是一种很好的解决办法。

六、虚拟滚动的原理

虚拟列表实际上就是使用少量的DOM节点显示长列表,即只创建并且显示我们视野中看到item节点,滚动过程中通过算法运算把视野中的节点更新成对应的节点。
在这里插入图片描述

7、实现思路

与懒加载不同,虚拟滚动需要一次性获取所有数据,但是只显示屏幕可见范围内的数据。

算了 不写这么多了

直接上代码吧 不难 看得懂 有注释
实现效果

定义变量

const demo = ref(null) // 外框盒子
const showNumber = 5 // 当前视窗展示条数
const itemHeight = 40 // 每一条内容的高度
const data = createData(1000) // 实际数据
let startNum = ref(0) // 当前视窗范围内第一个元素下标
let positionTop = ref(0) // 当前视窗范围内第一个元素偏移量
let lastTime = ref(0) // 最新的时间

滚动

<div ref="demo" class="scroll-box" :style="`height: ${showNumber * itemHeight}px;`">  // 可视区
  <div class="scroll-blank" :style="`height: ${data.length * itemHeight}px;`"> // 占位  使出现滚动
    <div class="scroll-data" :style="`top: ${positionTop}px;`"> // 渲染区域
      <div v-for="(item, index) in activeList" :key="item" class="scroll-item"> // 渲染的每一列
        {{ item }}
      </div>
    </div>
  </div>
</div>

<style>
 .scroll-box {
   margin: 200px auto;
   position: relative;
   overflow: auto;
   width: 400px;
   border: 1px solid rgb(0, 0, 0);
 }
 .scroll-data {
   position: absolute;
   width: 100%;
 }
 .scroll-item {
   box-sizing: border-box;
   border: 1px solid #fff;
   height: 40px;
   background: pink;
 }
 .scroll-item:hover {
   background: rgb(104, 111, 211);
   color: #fff;
 }
</style>

虚拟
仅渲染当前视窗内的内容,而对于超出的部分则进行移除

// 计算当前视窗内实际要渲染的内容
const activeList = computed(() => {
  const start = startNum.value
  return data.slice(start, start + showNumber)
})

什么时候对渲染的数据进行替换?
对外框盒子添加 scroll 的监听事件,在滚动的时候获取scrollTop的值并计算当前视窗范围内第一个元素的下标
在这里插入图片描述

// 滚动的时候计算当前视窗范围内第一个元素下标
const scrollEvent = (event) => {
  if (new Date().getTime() - lastTime > 10){
    const { scrollTop } = event.target
    startNum.value = parseInt(scrollTop / itemHeight)
    console.log(scrollTop, startNum.value)
    positionTop.value = scrollTop
    // positionTop.value = scrollTop - ( scrollTop % itemHeight)
    lastTime = new Date().getTime() //更新最新时间
  }
}

onMounted(() => {
  lastTime = new Date().getTime()
  demo.value.addEventListener('scroll', scrollEvent)
})
onUnmounted(() => {
  if (!demo.value) return
  demo.value.removeEventListener('scroll', scrollEvent)
  demo.value = null
})

完整代码
跑跑试试吧

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/vue@3.2.27/dist/vue.global.js"></script>
    <title>VirtualScroll</title>
  </head>
  <body>
    <div id="app">
      <div
        ref="demo"
        class="scroll-box"
        :style="`height: ${showNumber * itemHeight}px;`"
      >
        <div class="scroll-blank" :style="`height: ${data.length * itemHeight}px;`">
          <div class="scroll-data" :style="`top: ${positionTop}px;`">
            <div v-for="(item, index) in activeList" :key="item" class="scroll-item">
              {{ item }}
            </div>
          </div>
        </div>
      </div>
    </div>
    <script>
      const { computed, onMounted, onUnmounted, ref } = Vue

      const createData = (length) => {
        return Object.keys(new Array(length).fill(''))
      }
      const App = {
        setup() {
          const demo = ref(null) // 外框盒子
          const showNumber = 5 // 当前视窗展示条数
          const itemHeight = 40 // 每一条内容的高度
          const data = createData(1000) // 实际数据
          let startNum = ref(0) // 当前视窗范围内第一个元素下标
          let positionTop = ref(0) // 当前视窗范围内第一个元素偏移量
          let lastTime = ref(0) // 最新的时间

          // 计算当前视窗内实际要渲染的内容
          const activeList = computed(() => {
            const start = startNum.value
            return data.slice(start, start + showNumber)
          })
          console.log(activeList.value)

          // 滚动的时候计算当前视窗范围内第一个元素下标
          const scrollEvent = (event) => {
            if (new Date().getTime() - lastTime > 10){
              const { scrollTop } = event.target
              startNum.value = parseInt(scrollTop / itemHeight)
              console.log(scrollTop, startNum.value)
              positionTop.value = scrollTop
              // positionTop.value = scrollTop - ( scrollTop % itemHeight)
              lastTime = new Date().getTime() //更新最新时间
            }
          }

          onMounted(() => {
            lastTime = new Date().getTime()
            demo.value.addEventListener('scroll', scrollEvent)
          })
          onUnmounted(() => {
            if (!demo.value) return
            demo.value.removeEventListener('scroll', scrollEvent)
            demo.value = null
          })

          return {
            showNumber,
            itemHeight,
            demo,
            positionTop,
            data,
            activeList,
          }
        },
      }

      const app = Vue.createApp(App)
      app.mount('#app')
    </script>
    <style>
      .scroll-box {
        margin: 200px auto;
        position: relative;
        overflow: auto;
        width: 400px;
        border: 1px solid rgb(0, 0, 0);
      }
      .scroll-data {
        position: absolute;
        width: 100%;
      }
      .scroll-item {
        box-sizing: border-box;
        border: 1px solid #fff;
        height: 40px;
        background: pink;
      }
      .scroll-item:hover {
        background: rgb(104, 111, 211);
        color: #fff;
      }
    </style>
  </body>
</html>

再看另外一个demo
在这里插入图片描述
父组件

<template>
  <div class="personSocial">
    <ScrollComponent :data="dataList" :viewH="viewH" :itemH="itemH" />
  </div>
</template>

export default {
data () {
    return {
      dataList: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33],
      viewH: 200,
      itemH: 40
    }
 },
}

子组件

<template>
  <!-- 可视区盒子 -->
  <div :style="`height:${viewH}px;overflow-y:scroll`" @scroll="handleScroll" class="container1">
    <div :style="`height:${scrollH}px`" class="list">
      <div class="item_box" :style="`transform:translateY(${offsetY}px);`">
        <div class="item" :style="`height:${itemH}px`" v-for="(item,index) in list" :key="index">
          {{ item }}
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ScrollComponent',
  props: {
    data: Array,   // 列表总数据
    viewH: Number, // 外部高度
    itemH: Number, // 单项高度
  },
  data () {
    return {
      scrollH: '', // 整个滚动列表高度(总高度)
      list: [],    // 每次显示的数据
      showNum: '', // 页面需要显示的数量
      offsetY: '',// 动态偏移量- 外层的盒子进行滚动设置
      lastTime: '', //最新的时间
    }
  },
  mounted () {
    // 初始化计算
    this.scrollH = this.data.length * this.itemH
    // 计算可视化高度中能存几个列表,可以略多余可视化高度能存放的列表数量避免滚动时被替换
    this.showNum = Math.floor(this.viewH / this.itemH) + 1
    console.log(this.showNum);
    // 默认展示的几个数据
    this.list = this.data.slice(0, this.showNum)

    this.lastTime = new Date().getTime()
  },
  methods: {
    // handleScroll 滚动时候触发回调
    handleScroll (e) {
      // 控制滚动时间间隔
      if (new Date().getTime() - this.lastTime > 10) {
        let scrollTop = e.target.scrollTop //滚动的高度
        // 每一次滚动后 根据scrollTop值获取一个可以整除itemH结果进行偏移
        // 例如:scrollTop = 1220,1220 % this.itemH = 20 offsetY = 1220-20 = 1200
        this.offsetY = scrollTop - ( scrollTop % this.itemH )
        console.log(scrollTop, this.offsetY);

        console.log('卷入scrollTop值:', scrollTop, '卷入的行数:', Math.floor(scrollTop / this.itemH));

        this.list = this.data.slice(
          Math.floor(scrollTop / this.itemH), // 计算卷入了多少行
          Math.floor(scrollTop / this.itemH) + this.showNum
        )
        console.log(this.list);
        this.lastTime = new Date().getTime() //更新最新时间
      }
    }
  }
}
</script>

<style scoped>
.container1 {
  position: relative;
  background: #f1fffe;
  /* top: 50px; */
  /* left: 500px; */
  margin: 0 auto;
  border: 1px solid #fff;
  width: 500px;
}
.item {
  border: 1px solid pink;
}
</style>

https://mp.weixin.qq.com/s/kyZvTZgGy5CXVvaiter6cA

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

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

相关文章

Docker概念|容器|镜像|命令详细(创建,删除,修改,添加)

Docker概念|容器|镜像|命令详细&#xff08;创建&#xff0c;删除&#xff0c;修改&#xff0c;添加&#xff09; 一&#xff0c;Docker简介二&#xff0c;Docker与虚拟机的区别三&#xff0c;容器核心技术四&#xff0c;Docker核心概念五 docker的安装5.1关闭防火墙,关闭文件防…

python毕业设计之django+vue.js幼儿园网站系统

开发语言&#xff1a;Python 框架&#xff1a;django Python版本&#xff1a;python3.7.7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm 采用了Windows10操作系统平台&#xff0c;使用HTMLCSSJS前端模板django作为后台监控&#xff0…

【VM服务管家】VM4.x算子SDK开发_3.2 公用工具类

目录 3.2.1 图像载入&#xff1a;本地图像的载入方法3.2.2 相机取流&#xff1a;相机SDK取流的方法3.2.3 输入图像&#xff1a;给算子模块输入图像数据的方法3.2.4 实时取流&#xff1a;实时取流的实现方法3.2.5 卡尺ROI&#xff1a;卡尺型ROI的生成方法3.2.6 DL算子耗时&#…

ref在Vue2、Vue3中的使用

文章目录 前言一、ref在Vue2中的用法二、ref在Vue3中的用法 前言 记录一下ref在Vue2与Vue3中的使用&#xff0c;ref可以获取DOM元素&#xff0c;也可以获取子组件的数据、方法。 一、ref在Vue2中的用法 给元素绑定一个ref&#xff0c;然后在js中通过this.$refs获取DOM。 ref命…

从大厂到创业公司,管理上需要怎样转变?

你好&#xff0c;我是舒超。 我职业生涯过去十年的上半段在腾讯负责微博微群、消息流广告、视频评论等社交型的业务系统&#xff0c;下半段在美团基础架构负责云原生基础设施的演进工作&#xff0c;现在星汉未来担任CTO&#xff0c;负责公司产研推进工作。加入星汉未来的时间点…

带你搞懂人工智能、机器学习和深度学习!

不少高校的小伙伴找我聊入门人工智能该怎么起步&#xff0c;如何快速入门&#xff0c;多长时间能成长为中高级工程师&#xff08;聊下来感觉大多数学生党就是焦虑&#xff0c;毕业即失业&#xff0c;尤其现在就业环境这么差&#xff09;&#xff0c;但聊到最后&#xff0c;很多…

MongoDB【索引-index】

目录 1&#xff1a;概述 2&#xff1a;索引的类型 2.1&#xff1a;单字段索引 2.2&#xff1a;复合索引 2.3&#xff1a;其他索引 3&#xff1a;索引的管理操作 3.1&#xff1a;索引的查看 3.2&#xff1a;索引的创建 3.3&#xff1a;索引的移除 4&#xff1a;索引的…

微短剧“小阳春”,“爱优腾芒”抢滩登陆?

降本增效一整年&#xff0c;长视频平台们似乎扭转了市场对于它们“烧钱”的印象。 爱奇艺宣布2022全年盈利&#xff0c;腾讯视频宣布从去年10月起开始盈利&#xff0c;视频平台们结束了一场“无限战争”。 与此同时&#xff0c;随着短视频平台的崛起&#xff0c;视频内容的形…

【Hadoop-HDFS】HDFS中Fsimage与Edits详解

【Hadoop-HDFS】HDFS中Fsimage与Edits详解 1&#xff09;概述2&#xff09;NameNode元数据解析3&#xff09;Fsimage3.1.Fsimage 的作用3.2.FSimage 的文件信息查看 4&#xff09;Edits4.1.Edits 的作用4.2.Edits 的文件信息查看 5&#xff09;元数据信息目录的配置 1&#xff…

Shell+VCS学习1

Shell脚本常见问题 mkdir rmdir rm mkdir 创建文件夹 mkdir -p filename-p 确保目录名称存在&#xff0c;不存在的就建一个。 mkdir -p runoob2/test若 runoob2 目录原本不存在&#xff0c;则建立一个。&#xff08;注&#xff1a;本例若不加 -p 参数&#xff0c;且原本 ru…

栈和队列的转换

在之前的博客当中我们已经学习了栈和队列。在本次的博客当中我们就来学习一下怎么将栈和队列进行相互转换。 栈和队列的相互转换其实是两道OJ题。如果在leetcode上面刷过题的小伙伴们可能早就见过这两种数据结构的相互转换。下面我们就来分别讲解一下这两道OJ题目的编写思路。 …

如何基于vue实现倒计时效果

如何基于vue实现倒计时效果 基于vue2 element实现画面效果代码 基于vue2 element 实现画面效果 代码 <template><div><div class"Box"><div style"position: relative;"><el-progress type"circle" :width"…

使用注解实现REDIS分布式锁

一、业务背景 有些业务请求&#xff0c;属于耗时操作&#xff0c;需要加锁&#xff0c;防止后续的并发操作&#xff0c;同时对数据库的数据进行操作&#xff0c;需要避免对之前的业务造成影响。 二、分析流程 使用 Redis 作为分布式锁&#xff0c;将锁的状态放到 Redis 统一…

基于SpringBoot+Vue+Java的社区医院管理服务系统(附源码+数据库)

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括社区医院管理服务系统的网络应用&#xff0c;在外国线上管理系统已经是很普遍的方式&#xff0c;不过国内的管理系统可能还处于起步阶段。社区医院管理服务系统具有社区…

一道经典的小学数学题,和它背后的贪心算法(35)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 欢迎和猫妹一起&#xff0c;趣味学Python。 今日主题 这个五一小长假&#xff0c;你玩得怎么样&#xff1f; 今天&#xff0c;咱们先做一道经典的小学数学题&#xff0c;…

智慧物流信息系统开发需具备哪些功能?

智慧物流软件开发公司在制作管理系统的时候&#xff0c;需要具备的功能有哪些呢&#xff1f; 一、采集跟踪功能。 &#xff08;1&#xff09;、信息采集&#xff1a;信息采集跟踪系统是智能物流系统的重要组成部分。物流信息采集系统主要由RFID射频识别系统和Savan…

2022年度项目管理软件排名揭晓:哪些软件在市场中脱颖而出?

在项目管理软件的选择过程中&#xff0c;用户会倾向于参考一些软件排名来辅助自己进行选择。软件排名方面推荐参考G2&#xff0c;一个国外的靠谱软件评测网站&#xff0c;类似于软件版的“大众点评”&#xff0c;软件评价来自于真实用户&#xff0c;网站通过多维度的算法&#…

JAVA入坑之GUI编程

一、相关概述 GUI编程是指通过图形化的方式来实现计算机程序的编写&#xff0c;它可以让用户通过鼠标、键盘等设备来操作计算机&#xff0c;而不是通过命令行来输入指令。在Java中&#xff0c;GUI编程主要使用的是Swing和AWT两种技术 二、AWT 2.1介绍 AWT是Java提供的用来建立…

八部门联合推动IPv6创新发展 知道创宇助力IPv6快速安全改造

近日&#xff0c;工业和信息化部、中央网信办、国家发展改革委、教育部、交通运输部、人民银行、国务院国资委、国家能源局等八部门联合印发《关于推进IPv6技术演进和应用创新发展的实施意见》&#xff08;以下简称“《实施意见》”&#xff09;&#xff0c;提出到2025年底&…

密歇根大学Python 系列之三:Python 数据科学应用项目

Python在数据科学领域的应用已经成为了趋势&#xff0c;同时也在不断地发展和演化。对于从事数据科学相关工作的从业者来说&#xff0c;熟练掌握Python已经成为了必备技能之一。而对于其他从业者来说&#xff0c;了解Python在数据科学领域的应用也可以帮助他们更好地理解数据科…