学会前端虚拟滚动,看这篇就够了

一. 虚拟滚动是什么?

前端虚拟滚动是一种用于优化长列表或大量数据展示的技术。它的主要原理是只渲染用户当前可见区域的数据,而不是一次性渲染整个列表或数据集。

在传统的列表渲染中,如果数据量很大,渲染所有数据可能会导致性能下降,特别是在滚动时。虚拟滚动通过动态计算可见区域的位置和需要渲染的数据范围,只渲染可见区域内的部分数据,并在滚动时实时更新渲染的内容。

总结下就是:

  1. 接口返回了大量数据,前端将大量数据保存起来;
  2. 虚拟滚动的策略是:因为无论你返回了多大的数据量,用户可以看到的永远只是可视区域展示的数据,只是大量数据的很少一部分,所以保证可视区域的数据正常渲染就行。

二. 虚拟滚动怎么实现?

先上代码直观体验下

index.vue

javascript 复制代码
<template>
  <div class="container">
    <my-virtual-scroller :items="list" />
  </div>
</template>

<script>
import myVirtualScroller from './myVirtualScroller.vue'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
  list.push({
    id: i,
    label: `virtual-list ${i}`
  })
}
export default {
  components: {
    myVirtualScroller
  },
  data() {
    return {
      list: list
    }
  }
}
</script>

<style scoped>
.container {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

myVirtualScroller.vue

javascript 复制代码
// 传入数据可以实现虚拟滚动事件
// 这是一个封装好的子组件 传入数组进行渲染
<template>
  <!-- 最底层的可视区容器 -->
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <!-- 中间的可滚动区域,z-index=-1,高度和真实列表相同,目的是出现相同的滚动条 -->
    <div
      class="infinite-list-phantom"
      :style="{ height: listHeight + 'px' }"
    ></div>
    <!-- 最上层的可视区列表,数据和偏移距离随着滚动距离的变化而变化 -->
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px' }"
      >
        {{ item.label }}
      </div>
    </div>
  </div>
</template>
 
<script>
export default {
  name: "MyVirtualList",
  props: {
    //父组件传过来的数据
    items: {
      type: Array,
      default: () => [],
    },
    //可视区域每一项的高度 没有传参的话默认高度是100px
    itemSize: {
      type: Number,
      //没有高度的话 高度会默认是100px
      default: 100,
    },
  },
  computed: {
    //列表总高度
    listHeight() {
      // 计算当前传值数据的总长度 * 每个盒子的高度 计算出所有数据的整体高度
      return this.items.length * this.itemSize;
    },
    //可视区列表的项数
    visibleCount() {
      return Math.ceil(this.screenHeight / this.itemSize);
    },
    //可视区列表偏移距离对应的样式
    getTransform() {
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取可视区列表数据
    visibleData() {
      // 截取出可视区域显示的盒子 进行渲染
      return this.items.slice(
        this.start,
        Math.min(this.end, this.items.length)
      );
    },
  },
  mounted() {
    // 获取可是区域的高度
    this.screenHeight = this.$refs.list.clientHeight;
    // 开始
    this.start = 0;
    // 结束
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      screenHeight: 0, //可视区域高度
      startOffset: 0, //偏移距离
      start: 0, //起始索引
      end: 0, //结束索引
    };
  },
  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;
}
 
.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
}
 
.infinite-list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
}
 
.infinite-list-item {
  line-height: 50px;
  text-align: center;
  color: #555;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
</style>

虚拟滚动的思路总结如下:

1. 确定视窗位置:通过获取列表滚动区域的scrollTop属性,确定当前视窗的位置;

2. 计算需要渲染的节点:根据视窗位置和子节点的高度,计算出需要真实渲染的节点范围。可以使用Math.floor(scrollTop/itemHeight)和Math.ceil(viewHeight/itemHeight)来确定起始索引和渲染的节点数量;

3. 移动渲染的节点到视窗内:将需要渲染的节点移动到视窗内,可以通过修改节点的位置或使用其他方式实现;

4. 按需加载数据:根据滚动位置,动态加载相应的数据,只加载当前视窗可见范围内的数据,减少数据量和渲染负担;

5. 处理滚动事件:监听滚动事件,根据滚动位置实时更新需要渲染的节点和数据。

三. 虚拟滚动示例代码难点解析

1. 可视化容器为什么要包裹两个盒子,并且设置绝对定位?

第一个盒子(类名为"infinite-list-phantom")的作用:

其总高度为内容的总高度,主要要为了撑起父盒子,使父盒子有滚动条。

第二个盒子(类名为'infinite-list')的作用:

这个要可视区域,是用户所能看到的界面,也是数据渲染的界面。

至于为什么要设置绝对定位,是因为需要第二个盒子覆盖第一个盒子,以满足第二个盒子展现在可视界面。

2. 为什么要设置:style="{ transform: getTransform }"?

原因如下:

  1. 因为可视界面区域有限,在滚动后,会展示总的内容区域和可视区域不相交的部分,在可视区域就会出现部分或全部空白。
  2. 滚动的过程本质要内容区域的位置变化,其变化的数值可以通过scrollTop来查看;
  3. 通过transform的作用,抵消scrollTop的位置变动,使得我们看到的还是原先的可视区域位置的dom节点,只不过在滚动的过程中,渲染数据会改变。

3.为什么要设置偏移距离?

相关代码如下:

javascript 复制代码
 //此时的偏移距离
 this.startOffset = scrollTop - (scrollTop % this.itemSize);

原因如下:

  1. 为了使得滚动的时候有动态效果,体验会更好;
  2. 可以尝试将scrollTop - 改成 scrollTop + 测试,就可以更好的去理解2、3点疑问。
相关推荐
lovingsoft4 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly14 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝18 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦18 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡39 分钟前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码
葡萄城技术团队1 小时前
用 SpreadJS 数据验证实现省市区三级联动
前端