虚拟列表HTML结构关键在于正确容器、占位与定位:必须用带overflow-y:auto的固定高容器监听scrollTop;phantom需用height撑总高,禁用padding/transform;内容区须position:absolute+top定位,禁用transform;DOM须彻底替换而非隐藏;所有高度值必须为整数像素。

虚拟列表的 HTML 结构本身极简,关键不在“怎么写”,而在于“不能怎么写”——写错一行结构,滚动就错位、白屏、内存暴涨。
容器必须设 overflow-y: auto 且不能是 body
滚动监听依赖 scrollTop,而 document.body.scrollTop 在 Chrome/Firefox 中不可靠(尤其启用了 scroll-behavior: smooth 时)。必须用一个明确的、带 overflow-y: auto 的包裹容器。
- ✅ 正确:
<div id="list-container" style="height: 500px; overflow-y: auto; position: relative;"></div> - ❌ 错误:
<body style="overflow-y: auto">——scrollTop可能为 0 或不更新 - ❌ 错误:容器没设
height或max-height—— 滚动条不出现,scrollTop始终为 0
phantom 占位元素必须撑满总高度,且不能用 padding 或 transform
它负责撑开滚动空间,让浏览器知道“这个列表本该有多长”。用 padding 或 transform 撑高会干扰滚动计算,且在缩放/高 DPI 屏下易失准。
- ✅ 正确:
<div class="phantom" style="height: ${totalItems * itemHeight}px"></div> - ❌ 错误:
style="padding-top: ${totalItems * itemHeight}px"—— padding 变化触发 layout,且无法精确控制底部对齐 - ❌ 错误:
style="transform: translateY(${totalItems * itemHeight}px)"—— 不占文档流,无法撑滚动高度
真实内容区必须用 position: absolute + top 定位,禁用 transform: translateY
transform 虽快,但在部分 Chrome 版本(115–122)中会意外创建额外合成层,GPU 内存飙升;top 更可控,且与 phantom 高度天然对齐。
立即学习“前端免费学习笔记(深入)”;
- ✅ 正确:
<div class="items" style="position: absolute; top: ${startIndex * itemHeight}px; width: 100%;"></div> - ❌ 错误:
style="transform: translateY(${startIndex * itemHeight}px)"—— 合成陷阱 + 与 phantom 高度脱节 - ❌ 错误:内容区没设
position: absolute,或父容器没设position: relative——top会相对于 body 偏移,滚动错乱
DOM 复用必须清空旧节点,不能靠 v-if 或 display: none
虚拟列表的核心是“不创建”,不是“藏起来”。留着旧节点等于留着内存、事件监听器、Vue/React 响应式依赖——滚动越快,泄漏越严重。
- ✅ 正确:
contentEl.replaceChildren(...newFragments)(现代浏览器)或contentEl.innerHTML = ''+ 批量 append - ❌ 错误:
v-if="isVisible"或style="display: none"—— DOM 节点仍在内存里,浏览器照算布局 - ❌ 错误:直接
innerHTML += newHtml—— 旧节点残留,input 失焦、事件重复绑定、key 冲突全发生
最常被忽略的一点:所有高度值(itemHeight、container.clientHeight、phantom.height)必须用整数像素。小数会导致 Math.floor(scrollTop / itemHeight) 计算偏移,快速滚动时跳行或漏行——这不是 bug,是线性映射失效的必然结果。



















