前端虚拟长列表

当一次性渲染十万条 DOM 节点时,浏览器会瞬间陷入「卡顿---白屏---崩溃」三连击。虚拟长列表(Virtual Scroller)把「按需渲染」做到极致:只绘制可见区域并加少量缓冲,让巨量数据在低端设备也能保持 60 FPS。

一、问题本质:渲染成本与滚动成本的矛盾

渲染成本等于节点数量乘以单个节点复杂度,滚动成本等于布局重排乘以样式重绘。浏览器单帧预算约 16 ms,若一次回流就耗时 30 ms,动画必然掉帧。虚拟化的核心思路是把 O(N) 的渲染复杂度降为 O(屏幕可显示的最大条数)。

二、设计总览:三段式流水线

度量层:计算总高度,撑开滚动条,欺骗浏览器这里真的有十万条。

切片层:监听 scroll,根据滚动距离反推出首条索引与尾条索引。

渲染层:用绝对定位把切片渲染到正确位置,维持视觉连续性。

三、缓冲区与索引计算

获取当前滚动距离 scrollTop 与容器可视高度 clientHeight,先计算首条索引 startIndex 与尾条索引 endIndex,再前后各扩展 prev/next 条作为缓冲,避免快速滚动时出现空白闪烁。startPos 为首条切片距离容器顶部的绝对偏移量,用于后续 translateY。

js 复制代码
const startIndex = Math.floor(scrollTop / itemSize) - prev
const endIndex   = Math.ceil((scrollTop + clientHeight) / itemSize) + next
const startPos   = startIndex * itemSize

随后用 slice 取出数据区间并映射成渲染池 pool,每个元素携带原始 item 与 position。

四、绝对定位与 transform 的选择

top 会触发重排,transform 只触发合成层重绘。合成层由 GPU 处理,主线程压力降低 80% 以上。搭配 will-change: transform 提前提升图层,低端机也能稳住 60 FPS。

五、性能陷阱与修复要点

动态行高场景下,度量层计算失准,可用预扫描或 ResizeObserver 缓存每行真实高度。快速滚动出现白屏闪烁时,可加大 prev/next 缓冲量,并用 requestAnimationFrame 节流 scroll 事件。组件卸载时务必移除 scroll 监听器,防止内存泄漏。虚拟行内部若使用 v-model,每次输入都会触发全表更新,可改用 .lazy 或手动提交,避免动画掉帧。

六、代码实例

vue 复制代码
<template>
  <div class="scroller" @scroll="update">
    <div class="spacer" :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="row"
      v-for="row in pool"
      :key="row.key"
      :style="{ transform: `translateY(${row.y}px)` }"
    >
      {{ row.item.text }}
    </div>
  </div>
</template>

<script>
export default {
  props: {
    items: Array,
    itemHeight: { type: Number, default: 50 }
  },
  data: () => ({ pool: [] }),
  computed: {
    totalHeight() {
      return this.items.length * this.itemHeight;
    }
  },
  methods: {
    update() {
      const st = this.$el.scrollTop;
      const ch = this.$el.clientHeight;
      const start = Math.floor(st / this.itemHeight);
      const end   = Math.ceil((st + ch) / this.itemHeight);
      const y = start * this.itemHeight;
      this.pool = this.items.slice(start, end).map((item, i) => ({
        key: item.id,
        item,
        y: y + i * this.itemHeight
      }));
    }
  },
  mounted() {
    this.update();
  }
};
</script>

<style>
.scroller { height: 400px; overflow: auto; position: relative; }
.spacer   { position: absolute; top: 0; left: 0; right: 0; }
.row      { position: absolute; left: 0; right: 0; height: 50px; }
</style>
相关推荐
2501_938780282 分钟前
Ionic + Angular 跨端实战:用 Capacitor 实现相机拍照功能并适配移动端
前端·数码相机·angular.js
m0_64880493_江哥15 分钟前
Python实现随机选播视频的示例代码
前端·python·音视频
_大学牲16 分钟前
禁止复制内容 🤔 那点事~
前端·chrome·程序员
申阳21 分钟前
Day 1:耗时2小时,梳理并分享我作为全栈开发最依赖的“兵器库”
前端·后端·程序员
软件测试媛43 分钟前
14:00开始面试,14:06就出来了,问的问题有点变态。。。
面试·职场和发展
UIUV1 小时前
CSS 高级动画学习笔记 —— 从 “亲吻小球” 案例看 CSS 核心技术
前端·css
Java水解1 小时前
2026java面试题(含答案,持续更新中)
java·后端·面试
星链引擎1 小时前
智能聊天机器人 技术架构核心实现与场景化落地
前端
yoyoma1 小时前
彻底搞懂 JavaScript 闭包:原理、陷阱与内存优化全解析
前端·javascript
茄汁面1 小时前
Angular(TypeScript ) 中基于 ExcelJS 实现导入模板下载功能(带样式与数据验证)
前端·javascript·node.js