iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >vue实现虚拟列表组件解决长列表性能问题
  • 220
分享到

vue实现虚拟列表组件解决长列表性能问题

2024-04-02 19:04:59 220人浏览 泡泡鱼
摘要

目录一、虚拟列表二、实现思路难点与思考:其他注意事项:三、实现最终实现效果实现代码模拟数据的后端代码四、封装为组件props:event:虚拟列表组件代码使用代码最近项目中需要用到列

最近项目中需要用到列表的展示,且不分页。当数据加载太多时会造成性能问题。因此采用虚拟列表来优化

一、虚拟列表

真实列表:每条数据都展示到html上,数据越多,DOM元素也就越多,性能也就越差。

虚拟列表:只展示部分数据(可见区域展示数据),当屏幕滚动时替换展示的数据,DOM元素的数量是固定的,相比较真实列表更高效。

二、实现思路

难点与思考:

1. 如何计算需要渲染的数据

  • 数据可分为总数据,与需要渲染的数据,需要渲染的数据包括了可见区域与缓冲区域的数据
  • 通过单条数据占位的高度与可见区域的高度,算出可见区域的列表条数,再往上和往下扩展几条缓冲区域的数据(本次代码是以3倍可见区域的条数作为需要渲染的数据条数)

2. 何时替换数据

  • 监听滚动事件,渲染元素的第一条数据滚动出缓冲区域后(也就是可见区域第一个元素的index大于缓冲区域的条数时),就开始替换数据了,每次往上滑动一个元素,就替换一次数据。

3. 为何需要空白占位,如何计算空白占位的高度

  • 由于列表在滚动过程中会替换数据,如果没有空白占位的话,会导致第一个元素消失后,第二个元素立马替换了第一个元素的位置,会导致错位。如下图所示:

  • 因此滚动时,需要在元素消失后,补一个相同高度的空白占位
  • 上方的空白占位 = 消失的元素个数(也就是第一个渲染元素的index) * 单个元素的高度
  • 下方的空白占位 = 剩下需要渲染的元素个数(也就是最后一个元素的index与总数据条数的差值)* 单个元素的高度

其他注意事项:

  • 在使用v-for遍历渲染数据时,key的值使用index,不用itemid,可以避免该dom元素被重新渲染,只替换数据。
  • 下拉加载更多时,不要将整个数据替换了,而是追加到数据的后面,避免之前展示的数据被替换了。
  • 空白占位可以使用padding来占位,也可以使用DOM元素占位,使用DOM元素占位监听滚动事件时,应使用touchmovemousemove监听,避免dom元素高度变化后,又触发了scroll滚动事件。
  • 监听滚动事件应该采用节流的方式,避免程序频繁执行。
  • 监听滚动时加上passive修饰符,可以提前告知浏览器需要执行preventDefault,使滚动更流畅,具体功能可以参考Vue官网。
  • 外层包裹的元素需要有固定高度,并且overflowauto,才能监听scroll滚动事件。

三、实现

最终实现效果

实现代码

<template>
  <div id="app">
    <!-- 监听滚动事件使用passive修饰符 -->
    <div class="container" ref="container" @scroll.passive="handleScroll">
      <div :style="paddingStyle">
        <!-- key使用index,可避免多次渲染该dom -->
        <div class="box" v-for="(item, index) in showList" :key="index">
          <h2>{{ item.title }} - {{ item.id }}</h2>
          <h3>{{ item.from }}</h3>
        </div>
        <div>到低了~~~</div>
      </div>
    </div>
  </div>
</template>

<script>
import axiOS from "axios";
export default {
  name: "App",
  data() {
    return {
      allList: [], // 所有数据 
      isRequest: false,// 是否正在请求数据
      oneHeight: 150,  // 单条数据的高度
      showNum: 0,    // 可见区域最多能展示多少条数据
      startIndex: 0,   // 渲染元素的第一个索引
      canScroll: true,  // 可以监听滚动,用于节流
      scrollTop: 0,// 当前滚动高度,再次返回页面时能定位到之前的滚动高度
      lower: 150,// 距离底部多远时触发触底事件
    };
  },
  created() {
    this.getData();// 请求数据
  },
  activited() {
    this.$nextTick(()=>{
      // 定位到之前的高度
      this.$refs.container.scrollTop = this.scrollTop
    })
  },
  mounted() {
    this.canShowNum();  // 获取可见区域能展示多少条数据
    window.onresize = this.canShowNum;  // 监听窗口变化,需要重新计算一屏能展示多少条数据
    window.onorientationchange = this.canShowNum;  // 监听窗口翻转
  },
  computed: {
    // 渲染元素最后的index
    endIndex() {
      let end = this.startIndex + this.showNum * 3; // 3倍是需要预留缓冲区域
      let len = this.allList.length
      return end >= len ? len : end;  // 结束元素大于所有元素的长度时,就取元素长度
    },
    // 需要渲染的数据
    showList() {
      return this.allList.slice(this.startIndex, this.endIndex)
    },
    // 空白占位的高度
    paddingStyle() {
      return {
        paddingTop: this.startIndex * this.oneHeight + 'px',
        paddingBottom: (this.allList.length - this.endIndex) * this.oneHeight + 'px'
      }
    }
  },
  methods: {
    // 请求数据
    getData() {
      this.isRequest = true  // 正在请求中
      axios.get("Http://localhost:4000/data?num=10").then((res) => {
        // 将结果追加到allList
        this.allList = [...this.allList, ...res.data.list];
        this.isRequest = false
      });
    },
    // 计算可见区域能展示的条数
    canShowNum() {
      // ~~ 按位两次取反,得到整数
      this.showNum = ~~(this.$refs.container.offsetHeight / this.oneHeight) + 2;
    },
    // 监听滚动
    handleScroll(e) {
      if (this.canScroll) {
        this.canScroll = false
        // 处理数据
        this.handleData(e)
        // 节流
        let timer = setTimeout(() => {
          this.canScroll = true
          clearTimeout(timer)
          timer = null
        }, 30)
      }
    },
    handleData(e) {
      // 记录当前元素滚动的高度
      this.scrollTop = e.target.scrollTop
      // 可见区域第一个元素的index
      const curIndex = ~~(e.target.scrollTop / this.oneHeight)
      // 渲染区域第一个元素的index,这里缓冲区域的列表条数使用的是this.showNum
      this.startIndex = curIndex < this.showNum ? 0 : curIndex - this.showNum
      // 滚动距离底部,还有this.lower距离时,触发触底事件,正在请求中不发送数据
      if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - this.lower && !this.isRequest) {
        this.getData()
      }
    }
  },
};
</script>

<style>
#app {
  height: 100vh;
}
.container {
  height: 100%;
  
  overflow: auto;
}
.box {
  width: 96vw;
  height: 150px;
  background: #eee;
  border: 2px navajowhite solid;
  box-sizing: border-box;
}
</style>

模拟数据的后端代码

  • 这是本次用于模拟后端数据的代码,采用mockexpress
const Mock = require('mockjs')
const express = require('express')
const app = express()

let sum = 1 // mock的ID

// 根据入参生成num条模拟数据
function generatorList(num) {
  return Mock.mock({
    [`list|${num}`]: [
      {
        'id|+1': sum,
        title: "@ctitle(15,25)",
        from: "@ctitle(3,10)",
      }
    ]
  })
}
// 允许跨域
app.all('*', function (req, res, next) {
  res.setHeader("Access-Control-Allow-Origin", '*');
  res.setHeader("Access-Control-Allow-Headers", '*');
  res.setHeader("Access-Control-Allow-Method", '*');
  next()
})
app.get('/data', function (req, res) {
  const { num } = req.query
  const data = generatorList(num)
  sum += parseInt(num)
  return res.send(data)
})
const server = app.listen(4000, function () {
  console.log('4000端口正在监听~~')
})

四、封装为组件

也可以封装为插件,此处为了方便就封装为组件

props:

  • allList : 所有数据
  • oneHeight : 单条元素的高度
  • lower : 距离底部多远时触发触底事件,默认50

event:

  • @scrollLower : 触底时触发

虚拟列表组件代码

<template>
    <!-- 监听滚动事件使用passive修饰符 -->
    <div class="container" ref="container" @scroll.passive="handleScroll">
      <div :style="paddingStyle">
        <!-- key使用index,可避免多次渲染该dom -->
        <div v-for="(item, index) in showList" :key="index">
          <!-- 使用作用域插槽,将遍历后的数据item和index传递出去 -->
          <slot :item="item" :$index="index"></slot>
        </div>
        <div>到低了~~~</div>
      </div>
    </div>
</template>

<script>
export default {
  name: "App",
  props:{
    // 所有数据
    allList:{
      type:Array,
      default(){
        return []
      }
    },
    // 单条数据的高度
    oneHeight:{
      type:Number,
      default:0
    },
    // 距离底部多远时触发触底事件
    lower:{
      type:Number,
      default:50
    }
  },
  data() {
    return {
      showNum: 0,    // 可见区域最多能展示多少条数据
      startIndex: 0,   // 渲染元素的第一个索引
      canScroll: true,  // 可以监听滚动,用于节流
      scrollTop: 0,// 当前滚动高度,再次返回页面时能定位到之前的滚动高度
    };
  },
  activited() {
    this.$nextTick(()=>{
      // 定位到之前的高度
      this.$refs.container.scrollTop = this.scrollTop
    })
  },
  mounted() {
    this.canShowNum();  // 获取可见区域能展示多少条数据
    window.onresize = this.canShowNum;  // 监听窗口变化,需要重新计算一屏能展示多少条数据
    window.onorientationchange = this.canShowNum;  // 监听窗口翻转
  },
  computed: {
    // 渲染元素最后的index
    endIndex() {
      let end = this.startIndex + this.showNum * 3; // 3倍是需要预留缓冲区域
      let len = this.allList.length
      return end >= len ? len : end;  // 结束元素大于所有元素的长度时,就取元素长度
    },
    // 需要渲染的数据
    showList() {
      return this.allList.slice(this.startIndex, this.endIndex)
    },
    // 空白占位的高度
    paddingStyle() {
      return {
        paddingTop: this.startIndex * this.oneHeight + 'px',
        paddingBottom: (this.allList.length - this.endIndex) * this.oneHeight + 'px'
      }
    }
  },
  methods: {
    // 计算可见区域能展示的条数
    canShowNum() {
      // ~~ 按位两次取反,得到整数
      this.showNum = ~~(this.$refs.container.offsetHeight / this.oneHeight) + 2;
    },
    // 监听滚动
    handleScroll(e) {
      if (this.canScroll) {
        this.canScroll = false
        // 处理数据
        this.handleData(e)
        // 节流
        let timer = setTimeout(() => {
          this.canScroll = true
          clearTimeout(timer)
          timer = null
        }, 30)
      }
    },
    handleData(e) {
      // 记录当前元素滚动的高度
      this.scrollTop = e.target.scrollTop
      // 可见区域第一个元素的index
      const curIndex = ~~(e.target.scrollTop / this.oneHeight)
      // 渲染区域第一个元素的index,这里缓冲区域的列表条数使用的是this.showNum
      this.startIndex = curIndex < this.showNum ? 0 : curIndex - this.showNum
      // 滚动距离底部,还有this.lower距离时,触发触底事件,正在请求中不发送数据
      if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - this.lower) {
        this.$emit('scrollLower') // 传递触底事件
      }
    }
  },
};
</script>

<style>
.container {
  height: 100%;
  
  overflow: auto;
}
</style>

使用代码

<template>
  <div id="app">
    <VScroll :allList="allList" :oneHeight="150" :lower="150" @scrollLower="scrollLower">
      <!-- 作用域插槽,使用slot-scope取出在组件中遍历的数据 -->
      <template slot-scope="{item}">
        <div class="box">
          <h2>{{ item.title }} - {{ item.id }}</h2>
          <h3>{{ item.from }}</h3>
        </div>
      </template>
    </VScroll>
  </div>
</template>

<script>
import axios from "axios";
import VScroll from "./components/VScroll.vue";
export default {
  name: "App",
  data() {
    return {
      allList: [], // 所有数据
      isRequest: false  // 是否正在请求数据
    };
  },
  created() {
    this.getData(); // 请求数据
  },
  methods: {
    // 请求数据
    getData() {
      this.isRequest = true; // 正在请求中
      axios.get("http://localhost:4000/data?num=10").then((res) => {
        // 将结果追加到allList
        this.allList = [...this.allList, ...res.data.list];
        this.isRequest = false;
      });
    },
    // 滚动到底部
    scrollLower() {
      if (!this.isRequest) this.getData()
    }
  },
  components: { VScroll }
};
</script>

<style>
#app {
  height: 100vh;
}
.box {
  width: 96vw;
  height: 150px;
  background: #eee;
  border: 2px navajowhite solid;
  box-sizing: border-box;
}
</style>

到此这篇关于在vue中实现虚拟列表组件,解决长列表性能问题的文章就介绍到这了,更多相关vue虚拟列表组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: vue实现虚拟列表组件解决长列表性能问题

本文链接: https://www.lsjlt.com/news/164612.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • vue实现虚拟列表组件解决长列表性能问题
    目录一、虚拟列表二、实现思路难点与思考:其他注意事项:三、实现最终实现效果实现代码模拟数据的后端代码四、封装为组件props:event:虚拟列表组件代码使用代码最近项目中需要用到列...
    99+
    2024-04-02
  • vue长列表优化之虚拟列表实现过程详解
    目录前言实现原理实现代码总结 前言 应用场景:后台一次性发送上千条或更多数据给前台 场景模拟:用户发起一个请求,后台发送了10w条数据 使用虚拟列表之前:前台需要生成10w...
    99+
    2024-04-02
  • vue虚拟列表如何实现
    本篇内容介绍了“vue虚拟列表如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!多数据渲染现在解决多数据渲染,相信大家可能会想到分页,触...
    99+
    2023-07-02
  • 基于Vue实现封装一个虚拟列表组件
    目录组件效果属性插槽封装过程滚动条正常显示加载渲染大量数据不卡顿能对列表数据进行操作增删等完整代码待完善正常情况下,我们对于数据都会分页加载,最近项目中确实遇到了不能分页的场景,如果...
    99+
    2023-03-07
    Vue封装虚拟列表组件 Vue 虚拟列表组件 Vue 虚拟列表
  • vue虚拟化列表封装的实现
    目录vue虚拟化列表封装将下面代码复制一份到自己的项目中 vue虚拟列表-vue-virtual-scroll-list使用场景安装使用vue虚拟化列表封装 将下面代码复制...
    99+
    2024-04-02
  • 结合康熙选秀讲解vue虚拟列表实现
    目录场景康熙选妃多数据渲染虚拟列表的概念实现基本实现场景 康熙选妃 话说这年是康熙五十三年,天下太平,天下无人不感叹这“康熙盛世”啊,康熙自己也是开心的不得了...
    99+
    2024-04-02
  • 基于Vue如何封装一个虚拟列表组件
    今天小编给大家分享一下基于Vue如何封装一个虚拟列表组件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。组件效果使用方法<...
    99+
    2023-07-05
  • Vue组件库ElementUI实现表格列表分页效果
    ElementUI实现表格列表分页效果教程,供大家参考,具体内容如下 Element UI 是一套采用 Vue 2.0 作为基础框架实现的组件库,一套为开发者、设计师和产品经理准备的...
    99+
    2024-04-02
  • vxe-list vue怎么实现下拉框的虚拟列表
    本篇内容主要讲解“vxe-list vue怎么实现下拉框的虚拟列表”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vxe-list vue怎么实现下拉框的虚拟列表”吧!vxe-...
    99+
    2023-06-30
  • vxe-list vue 如何实现下拉框的虚拟列表
    目录vxe-list vue下拉框的虚拟列表虚拟列表的实现原理接下来测试一下vue虚拟列表实现原理应用场景实现思路基础实现vxe-list vue下拉框的虚拟列表 vxe-table...
    99+
    2024-04-02
  • python列表切片超出长度问题怎么解决
    当切片的结束位置超出列表的长度时,Python会自动将结束位置设置为列表的最后一个元素的索引加1。因此,可以通过判断切片的结束位置是...
    99+
    2024-02-29
    python
  • JS基于VUE组件实现城市列表效果
    本文实例为大家分享了基于VUE组件实现城市列表效果的具体代码,供大家参考,具体内容如下 基本思想是,将城市列表数据缓存在本地 然后在页面上用JS实现即时模糊查询和首...
    99+
    2024-04-02
  • 域名列表不能为空问题怎么解决
    如果您在创建域名列表时遇到“域名列表不能为空”的问题,请检查以下几点:1. 您是否正确输入了域名列表。请确保每个域名之间用逗号或换行...
    99+
    2023-06-10
    域名列表 域名
  • Vue读取HTMLCollection列表的length为0问题怎么解决
    这篇“Vue读取HTMLCollection列表的length为0问题怎么解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“...
    99+
    2023-06-30
  • 使用react-virtualized实现图片动态高度长列表的问题
    目录开发中遇到的问题解决方案具体实现实际效果小结虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。虚拟列表是对长列表场景一种常见的优化,毕竟很少有人在列...
    99+
    2024-04-02
  • Vue组件设计之多列表拖拽交换排序功能实现
    目录1. 安装所需依赖2. 组件设计实现3. 组件使用示例在前端开发中,拖拽排序是一种提升用户体验非常好的方式,常见的场景有单列表拖拽排序,多列表拖拽交换排序,比如以下这种效果: ...
    99+
    2023-05-18
    vue拖拽交换排序 vue多列表拖拽交换排序 vue列表拖拽排序
  • 怎么在Vue中使用Element实现一个树列表组件
    怎么在Vue中使用Element实现一个树列表组件?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1、常规树列表控件的使用众所周知,一般界面很多情况涉及到树列表的处理,如类型...
    99+
    2023-06-15
  • VUE 列表过渡常见问题解答:轻松解决你的困惑
    过渡动画不生效 问题 使用 <transition> 包裹列表元素<li>后,过渡动画不生效。 解决方法 确保满足以下条件: 添加 CSS 过渡样式。<transition> 仅负责触发过渡,具体的...
    99+
    2024-02-11
    vue 列表过渡 常见问题 解答
  • python多维列表总是只转为一维数组问题解决
    正文 # 从X和Y中取出相应步长对应的数组并保存至x_data和y_data中 x_data = [] y_data = [] for i in range(len(K) - 24*...
    99+
    2024-04-02
  • 如何解决JS组件系列之Bootstrap Table冻结列功能IE浏览器兼容性问题
    小编给大家分享一下如何解决JS组件系列之Bootstrap Table冻结列功能IE浏览器兼容性问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作