虚拟滚动列表深度解析
一、问题为什么需要虚拟滚动当一个列表需要渲染 成百上千条数据 时传统的v-for全量渲染方案会创建等量的 DOM 节点。假设每条列表项高度 120px、包含 10 个子元素1000 条数据就意味着 10,000 个 DOM 节点。大量 DOM 节点带来的性能问题包括性能瓶颈首次渲染慢— 浏览器需要计算 10,000 个节点的布局、样式、绘制滚动卡顿— 每次滚动触发重排/重绘帧率暴跌内存占用高— 每个节点占用几十 KB万级节点轻松吃掉几百 MB数据更新慢— Vue 的 diff 算法需要遍历整个列表虚拟滚动的核心思想用户在任何时刻只能看到屏幕上的十几条数据其余的都是不可见的。既然不可见就不需要渲染 DOM。二、虚拟滚动核心原理图 1: 虚拟滚动核心原理 — 幻影层撑高度内容层只渲染可视区虚拟滚动由 三个关键层 组成1. 幻影层Phantom Div一个没有任何内容的div高度等于items.length × itemHeight。它的作用是撑起滚动容器的总高度让滚动条的比例和真实数据量一致。div classphantom :style{ height: totalHeight px }/div // totalHeight 1000条 × 120px 120,000px2. 内容层Content Div通过transform: translateY(offsetY)定位只包含当前可视区域的数据。通过 CSSwill-change: transform告知浏览器提前优化。div classcontent :style{ transform: translateY(${offsetY}px) } div v-foritem in visibleItems :keyitem.id {{ item.name }} /div /div3. 可视区间计算根据当前scrollTop和容器高度计算出应该渲染哪些条目startIndex floor(scrollTop / itemHeight) - bufferSize endIndex ceil((scrollTop containerHeight) / itemHeight) bufferSize offsetY startIndex × itemHeight其中bufferSize是上下缓冲区条数通常 3~5 条确保滚动时上下有预渲染内容避免快速滚动时出现白屏。三、Vue 3 实现一个通用虚拟列表组件下面是一个生产可用的VirtualList.vue组件支持slot自定义渲染、ResizeObserver自适应、requestAnimationFrame滚动节流。template div classvl-container refcontainerRef scrollonScroll div classvl-phantom :style{ height: totalHeight px }/div div classvl-content :style{ transform: translateY(${offsetY}px) } div v-foritem in visibleItems :keyitem._vKey classvl-item :style{ height: itemHeight px } slot :itemitem :indexitem._realIndex/slot /div /div /div /template script setup import { ref, computed, onMounted, onUnmounted, nextTick } from vue const props defineProps({ items: { type: Array, default: () [] }, itemHeight: { type: Number, default: 120 }, bufferCount: { type: Number, default: 5 } }) const containerRef ref(null) const scrollTop ref(0) const containerHeight ref(0) let ticking false const totalHeight computed(() props.items.length * props.itemHeight) const startIndex computed(() { let idx Math.floor(scrollTop.value / props.itemHeight) - props.bufferCount return Math.max(0, idx) }) const endIndex computed(() { let idx Math.ceil( (scrollTop.value containerHeight.value) / props.itemHeight ) props.bufferCount return Math.min(props.items.length, idx) }) const offsetY computed(() startIndex.value * props.itemHeight) const visibleItems computed(() { return props.items.slice(startIndex.value, endIndex.value) .map((item, i) ({ ...item, _realIndex: startIndex.value i })) }) const onScroll () { if (containerRef.value) { scrollTop.value containerRef.value.scrollTop } if (!ticking) { requestAnimationFrame(() { ticking false }) ticking true } } let resizeObserver null onMounted(() { nextTick(updateContainerHeight) resizeObserver new ResizeObserver(updateContainerHeight) if (containerRef.value) resizeObserver.observe(containerRef.value) }) onUnmounted(() { resizeObserver?.disconnect() }) /script设计要点slot 渲染— 通过#default{ item }让父组件决定每条数据的渲染方式组件本身不关心业务逻辑ResizeObserver— 监听容器尺寸变化窗口 resize 时自动重算可视区间RAF 节流— 滚动事件每秒触发几十次用requestAnimationFrame保证一帧只渲染一次四、多数据源合并方案实际项目中一个页面可能有 多个数据源如单站定位、TDOA、AOA、雷达、5G-A 等每个数据源独立接收 socket 推送。如果每个数据源各自渲染一个列表DOM 节点会成倍增长。解决方案合并成一条列表用虚拟滚动统一渲染。图 2: 多数据源合并架构 — 子组件接收数据父组件定时合并排序数据流设计// 父组件定时收集子组件数据 排序 const startMergeTimer () { setInterval(() { let all [ ...(singleStationRef.value?.getData() || []), ...(tdoaRef.value?.getData() || []), ...(aoaRef.value?.getData() || []), ...(radarRef.value?.getData() || []), ] // 按优先级升序同级按时间倒序 all.sort((a, b) { if (a.sourcePriority ! b.sourcePriority) return a.sourcePriority - b.sourcePriority return (b.timestamp || 0) - (a.timestamp || 0) }) mergedList.value all }, 500) }子组件改造三步走步骤改动目的1. 禁用自身渲染v-iffalse替换原来的条件组件仍然挂载、接收数据但不渲染列表2. 打标记 暴露方法数据加sourceType、sourcePriority、_vKeydefineExpose({ getData })父组件能识别数据来源并收集3. 去掉 scopedstyle langscss去掉scoped样式在 VirtualList slot 中也能生效五、超时自动清理机制每条数据代表一个持续推送的目标如一架无人机。当某个 socket 停止推送 后该数据超时未更新就需要自动从列表中删除。每个子组件在接收数据时记录时间戳定时检查是否超时// 子组件内定时清理过期数据 setInterval(() { let needRefresh false for (const key in onlyData.value) { if (Date.now() - onlyData.value[key].timestamp timeoutSeconds * 1000) { delete onlyData.value[key] // 彻底删除 needRefresh true } } if (needRefresh) { dataList.value Object.values(onlyData.value) } }, 1000)关键设计彻底删除 vs 过滤隐藏— 不要只做v-if过滤显示要用delete从源对象中删除否则数据永远占内存清理时机— 每秒检查一次即可不需要更频繁联动效果— 子组件删除数据后下一次 500ms 合并时父组件自动感知VirtualList 自然不再渲染该条目六、性能对比指标传统 v-for虚拟滚动提升1000 条数据 DOM 节点数~10,000~25400x首次渲染时间800ms50ms16x滚动帧率15-30 FPS58-60 FPS2-4x内存占用~300MB~15MB20x数据更新 diff遍历全量仅可视区N/A为什么定高是虚拟列表的前提定高让startIndex、endIndex、offsetY的计算成为 O(1) 的数学运算。如果高度不定就需要维护一个位置映射表每次二分查找复杂度变为 O(log n)。不定高度的替代方案如果业务上无法做到定高有两种方案1. 估算 实测修正— 先用估算高度渲染渲染后用ResizeObserver测量真实高度更新位置映射表2. 使用成熟库—vue-virtual-scroller、tanstack/vue-virtual都支持动态高度七、踩坑记录1. 子组件 v-if vs v-show改造子组件时必须用v-showfalse而不是v-iffalse。因为v-if会彻底销毁组件导致 socket 数据接收函数和定时器全部失效。2. 样式隔离问题Vue 的style scoped会给选择器加[data-v-xxx]属性限定。当卡片 HTML 从子组件搬到父组件的 slot 后scoped样式失效。解决方案是去掉子组件的scoped或用:deep()穿透。3. 上下间距过大虚拟列表的itemHeight是固定的如果卡片实际高度小于itemHeight中间就会出现空白。解决方案itemHeight设为卡片展开后的最大高度或使用动态高度方案。4. 翻译 key 作用域当卡片 HTML 搬到父组件 slot 后$t(xxx)的翻译 key 需要确保在父组件的 i18n 上下文中也能正确解析。如果子组件和父组件使用不同的 i18n 实例需要统一。八、总结虚拟滚动的本质是一个空间换时间的策略用少量 DOM 节点 数学计算模拟出完整列表的视觉效果。多数据源合并的关键在于子组件只负责数据接收和业务逻辑父组件负责统一渲染。核心公式实际 DOM 数 (containerHeight / itemHeight) bufferSize × 2 // 例(600 / 120) 5 × 2 15 个 DOM 节点渲染 1000 条数据这种方案特别适合实时推送场景如无人机监控、股票行情、物联网设备列表数据持续涌入的同时保持界面流畅。本文基于 Vue 3 Element Plus 实际项目经验总结。如有疑问欢迎讨论。

相关新闻

EMC BT Link (PER) 蓝牙链路误包率测试

EMC BT Link (PER) 蓝牙链路误包率测试

https://pan.baidu.com/s/1XOebJK1VDBJQo1s5uMWnyQ 提取码:7icn 通过 ADB 唤起 MTK8768 原生工程模式(最推荐,成功率最高) 这是 MTK 平台最标准的操作方法。即便你的设备去掉了系统自带的拨号盘,也可以通过 am 命令强制弹出底层测…

2026/7/11 2:53:43 阅读更多 →
[2014] [DPG] [Deterministic Policy Gradient Algorithms]

[2014] [DPG] [Deterministic Policy Gradient Algorithms]

Deterministic Policy Gradient Algorithms, 2014 随机策略: π θ ( a ∣ s ) = P [ a ∣ s ; θ ] π_\theta(a|s)=P[a|s;\theta] π

2026/7/11 2:51:42 阅读更多 →
Seedance 2.5 AI视频生成:从文字到流畅短视频的完整操作指南

Seedance 2.5 AI视频生成:从文字到流畅短视频的完整操作指南

1. 先搞清楚 Seedance 2.5 到底能帮你解决什么视频创作问题如果你经常需要快速制作短视频内容,无论是社交媒体运营、产品展示还是创意表达,Seedance 2.5 最值得关注的是它在动作流畅度和画面一致性上的提升。这不是一个需要专业剪辑技能的工具&#xff0…

2026/7/11 2:47:41 阅读更多 →

最新新闻

从人工巡查到数据智管:智能锁重构网约房民宿合规运营体系

从人工巡查到数据智管:智能锁重构网约房民宿合规运营体系

随着各地公安部门持续收紧网约房、分散式民宿监管标准,“实名、实人、实证、实时”四实登记制度成为行业合规经营的硬性底线。传统短租业态长期依赖人工登记、纸质备案、线下钥匙交接、固定密码开锁的运营模式,普遍存在身份核验流于形式、入住数据无法溯…

2026/7/11 5:38:28 阅读更多 →
C#行为树序列化陷阱与解决方案:循环引用、多态与版本兼容性

C#行为树序列化陷阱与解决方案:循环引用、多态与版本兼容性

1. 项目概述:当行为树遇上序列化陷阱如果你正在用C#开发游戏AI,尤其是Unity项目,那么行为树(Behavior Tree)大概率是你工具箱里的常客。它那清晰的可视化逻辑和强大的复用能力,让复杂的AI决策变得井井有条。…

2026/7/11 5:38:28 阅读更多 →
Unity ProBuilder 10大核心技巧:从零到一快速搭建3D场景原型

Unity ProBuilder 10大核心技巧:从零到一快速搭建3D场景原型

1. 项目概述:为什么ProBuilder是Unity开发者的建模利器?如果你刚开始接触Unity,或者一直苦于在Unity里快速搭建一个简单的场景原型需要反复在3D建模软件和引擎之间切换,那么ProBuilder这个工具你绝对不能错过。它不是一个独立的软…

2026/7/11 5:34:27 阅读更多 →
5分钟快速上手:Python WebSocket客户端终极指南

5分钟快速上手:Python WebSocket客户端终极指南

5分钟快速上手:Python WebSocket客户端终极指南 【免费下载链接】websocket-client WebSocket client for Python 项目地址: https://gitcode.com/gh_mirrors/we/websocket-client websocket-client是一个强大且易用的Python WebSocket客户端库,专…

2026/7/11 5:32:26 阅读更多 →
2026年8月施行!《网络数据安全风险评估办法》全文解读,企业合规必看

2026年8月施行!《网络数据安全风险评估办法》全文解读,企业合规必看

数据合规监管再升级!国家网信办正式发布《网络数据安全风险评估办法》(以下简称《办法》),作为《数据安全法》《个人信息保护法》《网络数据安全管理条例》的核心配套规章,补齐了数据风险评估的合规细则。 重点&#…

2026/7/11 5:30:26 阅读更多 →
Qt Quick 3D Model 组件 10 个核心属性详解:从 source 到 pickable

Qt Quick 3D Model 组件 10 个核心属性详解:从 source 到 pickable

Qt Quick 3D Model 组件 10 个核心属性深度解析与实战应用在三维应用开发中,模型渲染是构建沉浸式体验的核心环节。Qt Quick 3D 的 Model 组件作为场景中的基础元素,提供了丰富的属性配置来精确控制模型的表现形式。本文将深入剖析 Model 组件的 10 个关…

2026/7/11 5:30:26 阅读更多 →

日新闻

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南 【免费下载链接】comfyui_controlnet_aux ComfyUIs ControlNet Auxiliary Preprocessors 项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux 想要让AI图像生成真正听从你的指挥吗&…

2026/7/11 0:02:12 阅读更多 →
PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

1. 项目背景与核心需求 在工业控制、安防系统和智能家居等领域,可靠的声音警报系统是不可或缺的基础组件。传统蜂鸣器存在音量不足、音质模糊等问题,而基于压电陶瓷技术的EPT-14A4005P蜂鸣器配合PIC18F45K42微控制器,能够构建一套适应性强、音…

2026/7/11 0:04:12 阅读更多 →
大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm Attention 的 CUDA Kernel 手写与验证 一、GPU 利用率 30%:分开的算子吃掉所有带宽 推理服务的 GPU 利用率监测显示一个反直觉的现象:计算核心(SM)利用率不到 30%,但…

2026/7/11 0:04:12 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/10 19:03:29 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/9 13:46:46 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/9 21:41:05 阅读更多 →

月新闻