Vue3虚拟列表实现十万条数据流畅渲染
虚拟滚动通过只渲染可视区域数据解决大数据量列表性能问题。固定高度方案利用偏移量与translate3d提升渲染效率;动态高度方案采用位置缓存和二分查找定位,配合节流与预加载机制,实现流畅滚动。
1. 技术背景:虚拟滚动为何成为大数据量列表的解决方案
当列表数据量达到数千甚至上万条时,传统的渲染方式会尝试将全部 DOM 节点一次性插入页面,结果必然导致严重的性能瓶颈——页面卡顿明显、滚动响应迟钝,用户体验急剧下滑。那么,是否存在一种方案既能完整呈现数据集,又不会拖累浏览器性能?答案是肯定的。虚拟滚动(Virtual Scrolling)正是为解决这一痛点而生的前端优化技术。
其核心设计思路非常清晰:仅渲染当前可视区域内的那一部分数据,而不可见的内容在 DOM 中完全不出现。这样一来,无论列表后方有多少条数据,屏幕上始终只维持有限的节点数量,渲染效率与滚动流畅度均得到显著提升。

2. 固定高度方案(v1):基础实现与核心逻辑
2.1 核心实现原理
固定高度版本的实现遵循一个直观的思路:
- 采用一个固定高度的容器作为“视口”,相当于一扇窗户,用户透过它看到的即是列表当前展示的内容。
- 同时放置一个与全部数据总高度相等的占位元素,用于撑开滚动条——没有这个滚动“尾巴”,浏览器无法感知还有大量数据未被查看。
- 接下来需要动态计算:随着滚动条的滑动,实时算出当前视口中应该呈现哪些数据,然后仅渲染这些元素。
- 最后借助 translate3d 精确控制列表项的位置,这种方式性能出色,且不会触发回流与重排。
2.2 关键代码解析
模板结构
class="viewport" ref="viewport" @scroll="handleScroll">
class="scroll-bg" ref="scrollBg">
class="scroll-list"
ref="scrollList"
:style="{ transform: `translate3d(0, ${offset}px,0)` }"
>
v-for="item in visibleItems" :key="item.id">
item="item">
Props 定义
const props = defineProps<{
items: any[]; // 列表数据
remain: number; // 显示个数
size: number; // 每个元素的高度
}>();
核心状态管理
此处通过 start 和 end 标记当前屏幕应显示数据的起始与结束索引。但有一个关键细节:滚动时列表项可能只移动了一半,如果仅渲染完整的 start 到 end 区间,就可能出现空白区域。因此需要在前后各多加载一些数据,相当于准备约三个屏幕的数据量。
此外,利用 offset 控制列表外层容器的偏移量——用户每完整滑过一个项,偏移量就累加一个项的高度。最终对传入的总数据进行截取,只渲染屏幕所需的那部分,从而达到性能优化的目标。
// 数组的起始值
const start = ref(0);
const end = ref(props.remain);
// 数组渲染dom的偏移量
const offset = ref(0);// 前面预先加载的个数
const prevCount = computed(() => {
return Math.min(start.value, props.remain);
});
// 后面预先加载的个数
const nextCount = computed(() => {
return Math.min(props.items.length - end.value, props.remain);
});
// 计算当前需要显示的数据
const visibleItems = computed(() => {
const startIndex = start.value - prevCount.value;
const endIndex = end.value + nextCount.value;
return props.items.slice(startIndex, endIndex);
});
滚动事件处理
监听滚动事件:获取当前滚动距离,计算在此过程中完整滑过的列表项数量,然后据此重新计算 start 与 end,并同步更新偏移量。
const handleScroll = () => {
// 滚动的距离
const scrollTop = viewportRef.value?.scrollTop ?? 0;
// 滚动过去的完整个数
const scrollCount = Math.floor(scrollTop / props.size);
start.value = scrollCount;
end.value = start.value + props.remain;
offset.value = start.value * props.size - prevCount.value * props.size;
};
2.3 性能优化策略
- 仅渲染当前可视区域的元素,大幅削减 DOM 节点数量
- 借助 CSS 硬件加速(translate3d),避免每次滚动都触发重排
- 在首尾设置预加载缓冲区,防止快速滚动时出现白屏
- 利用 Vue 的响应式系统精确更新数据切片,提升计算效率
2.4 使用示例
:size="40" :items="items" :remain="10">
"{ item }">
:title="item.title">
</template>
</template>
3. 动态高度方案(v2):应对复杂业务场景的进阶实现
固定高度方案虽然高效实用,但在真实业务中,列表项的高度往往参差不齐。例如,某列表的第一条可能是简短文案,第二条却包含图片加多行说明,高度差异明显。此时必须支持动态高度,才能满足更复杂的业务需求。
3.1 核心实现原理
动态高度方案的核心在于两项关键技术:位置缓存与二分查找。
3.1.1 位置缓存
- 为每一项维护一条记录,存储其 top、bottom、height 三个数值
- 初始时所有项均以默认高度预估,待实际渲染完成后,再用真实高度替换
- 一旦某项目高度发生变化,其后所有项的位置都需要重新校正
3.1.2 二分查找
- 如何快速定位滚动位置对应的起始索引?若逐个向前推算,数据量庞大时性能会急剧下降
- 因此采用二分查找,将定位时间复杂度从 O(n) 降至 O(log n),大幅提升检索效率
3.2 算法详解
3.2.1 二分查找算法
此二分查找实现稍有特殊:用户滚动的位置可能落在某个项的正中间,因此当找到对应位置时,并不一定恰好位于某个项的开头。为此,使用一个 temp 变量记录当前最接近滚动位置的索引。
const binarySearch = (scrollTop) => {
let start = 0;
let end = positions.length - 1;
let temp = null;
while (start <= end) {
let mid = (start + end) | 0;
let midBottom = positions[mid].bottom;
if (scrollTop === midBottom) {
return mid + 1;
} else if (scrollTop < midBottom) {
if (temp === null || temp > mid) {
temp = mid;
}
end = mid - 1;
} else {
start = mid + 1;
}
}
return temp || 0;
};
3.2.2 位置更新算法
当某个列表项的实际高度被测量出来后,需与缓存中的旧高度进行比对。若存在差异,则更新当前位置,同时该位置之后的所有项都需要重新计算 top 和 bottom。
const { height } = el.getBoundingClientRect();
const id = Number(el.getAttribute("vid")) || 0;
const oldHeight = positions.find((p) => p.id === id)?.height ?? 0;
const diffHeight = height - oldHeight;
if (diffHeight !== 0) {
// 高度有变化
const index = positions.findIndex((p) => p.id === id);
positions[index]!.height = oldHeight + diffHeight;
positions[index]!.bottom = positions[index]!.top + height;
//后面的都需要更新
for (let i = index + 1; i < positions.length; i++) {
positions[i]!.top = positions[i - 1]!.bottom;
positions[i]!.bottom = positions[i]!.top + positions[i]!.height;
}
}
3.3 性能优化
3.3.1 节流处理
滚动事件触发极为频繁,因此借助 lodash.throttle 进行节流控制,默认间隔设为 100ms。这样既不影响用户的滚动体验,又能避免高频触发引发的大规模计算开销。
3.3.2 预加载机制
与前一个版本类似,通过前后预加载项数(prevCount / nextCount)防止快速滚动时出现白屏。实际效果显著,即使数据量达到上万条,用户依然能够顺畅滚动,体验如同浏览普通列表一般自然。
团队介绍
「智慧家技术平台-智家APP开发」通过持续迭代演进移动端一站式接入平台,为三翼鸟APP、智家APP等多个应用提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;同时依托Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布到线上实时监控等DevOps与工程效能基础设施的搭建。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何用拖放在Flask看板应用中更新数据库状态
在Flask看板应用中,通过HTML5拖放API结合前端fetch异步POST请求,将拖拽任务的task_id和target_state发送至后端路由;后端接收JSON后更新数据库并提交事务,实现跨列拖拽时实时更新状态。需注意将事件绑定到容器而非子元素,并在ondragover中阻止默认行为以确保drop触发。
如何用CSS :has()选择器快速实现悬停span显示相邻div
利用CSS的:has()伪类选择器,通过` bar:has(> baz:hover)+ qux`实现悬停内联元素时显示相邻块级容器,解决了传统相邻兄弟选择器无法跨层级匹配的局限,可用于悬停图标显示详情框等交互场景。需注意浏览器兼容性和性能优化,避免过度使用影响渲染效率。
display:block居中段落文本的响应式实现方法
通过`display:block`与`max-width`限制宽度,配合`margin:auto`使段落块水平居中并实现响应式布局;同时用CSS统一控制段落间距,避免脱离文档流,提升可读性与维护性。
JavaScript代码去重:高效避免重复编写的技巧
将重复代码中的变化点提取为参数,利用ES6模板字符串动态生成选择器,可消除重复、保持一致性。注意参数安全性,必要时进行白名单校验;批量化场景可扩展为数组形式;现代框架中更推荐数据驱动或组件化封装替代手动选择器。
使用容器查询单位cqw实现表单输入框相对于祖父容器百分比宽度
利用CSS容器查询单位cqw,表单输入框宽度可直接基于祖父容器计算,绕过中间包装器尺寸干扰,实现简洁响应式布局,无需硬编码或JavaScript,显著降低维护成本,提升嵌套结构下的布局灵活性。
- 热门数据榜
相关攻略
2026-07-09 07:01
2026-07-09 07:01
2026-07-09 07:01
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

