移动端长列表卡顿根源于布局与渲染策略耦合,需固定滚动容器高度、用 transform 定位列表项、非数据节点数据化、滚动监听加 passive 选项并配合 requestAnimationFrame 节流。

移动端长列表渲染卡顿,根本不是“加个 loading 就能解决”的问题——它直接暴露了布局与渲染策略的耦合缺陷。不改布局结构,光在 JS 层做节流或懒加载,最多延缓崩溃,无法根治。
滚动容器必须设固定高度 + overflow-y: auto
这是所有虚拟滚动计算的前提。如果用 height: auto、min-height,或父容器是 flex 且没设高,scrollTop 值会跳变甚至归零,可视区域索引直接失效。
- 正确写法:
height: 400px; overflow-y: auto;(具体数值按业务场景定) - 该元素不能被父级
overflow: hidden裁剪,否则滚动事件不触发 - React 中 ref 必须绑在这个带
overflow的容器上,不是外层 wrapper - Vue 中若用
v-virtual-scroll,需确认文档是否要求 wrapper 显式设高
列表项必须用 transform 定位,禁用 margin-top 累加
用 margin-top 推每一项位置,等于让浏览器每帧都重排整个容器;而 transform: translateY() 是合成层操作,不触发重排,性能差一个数量级。
- 每个
li或div必须设position: absolute或用transform计算偏移 - 容器本身加
contain: layout paint,告诉浏览器“不可见区域不用绘制” - 别用
top+position: relative模拟,它仍会参与重排流程
非数据节点(如广告、分组标题)必须当数据项处理
在虚拟列表中间硬塞一个 <div class="ad-banner">,会导致索引错位、translateY 偏差、滚动跳变——因为虚拟逻辑只认“纯数据流”。
立即学习“前端免费学习笔记(深入)”;
- 把广告、分组标题也塞进
items数组,用type字段区分(如{ type: 'ad', content: '...' }) - 渲染函数里用
if (item.type === 'ad')分支处理,保持索引连续 - 框架如
react-window或vue-virtual-scroller基本不支持 slot 插入非受控内容
滚动监听必须加 { passive: true }
不加这个选项,Chrome 会强制同步执行 handler,拖垮帧率。尤其在 iOS 上,滚动卡顿往往就出在这里。
- 原生写法:
container.addEventListener('scroll', handler, { passive: true }) - React 中用
useEffect绑定时,务必传入该配置 - 别用
onScroll直接写在 JSX 上(如<div onScroll={...}>),它默认不 passive - 配合
requestAnimationFrame节流,避免高频触发导致 JS 主线程阻塞
真正难的不是写对 transform 或算对 scrollTop,而是把“布局约束”刻进组件设计第一行:容器高度不可变、节点必须可复用、非数据内容必须数据化——漏掉任何一条,虚拟滚动就退化成障眼法。



















