涉及到的业务逻辑
使用van-swipe显示1000道题的翻页滑动
vant的真实机制(虚拟懒挂载)是怎么回事?
van-swipe 在 Vant 3.x / 4.x 内部,采用了 lazy render + 复用 track 的实现方式。
它不会一次性把所有 的内容挂载到 DOM,而是动态控制 “挂载数量窗口”。
1.初次渲染
- 默认只渲染当前页 + 前一页 + 后一页(共3个 item);
- 其余 item 只是预留 <div class=“van-swipe-item”> 容器,但内容为空。
2.滑动时 - 当滑动到第 N 页时,组件会懒挂载第 N+1 页内容;
- 同时可能卸载最早的页面(具体取决于内部缓存策略)。
3.观察 DOM - 一开始只看到 3 个真实内容;
- 不断滑动时,van-swipe 的虚拟列表动态增加;
- 但不会一次性加载 100 页的内容到内存中。
问题1:如果滑动到第N页时,会懒挂载第N+1页内容,那是不是100个DOM同时存在?为什么不会造成卡顿呢?
每个 本身只是一个容器;
图片/视频等资源懒加载的仍然是懒加载;
Vue 会复用响应式对象引用,销毁的少;
现代浏览器对不可见节点有 GPU / JS 优化(不会持续绘制)。
换句话说:
看似 100 个节点都在,但 GPU 只渲染当前和邻近几屏。
所以仍然能保持顺滑。
问题2:为什么即使DOM节点增多,页面依旧顺滑
这背后其实涉及浏览器的渲染管线(Rendering Pipeline)、
Vue 的响应式对象复用机制、
以及 GPU/JS 层的“懒绘制”和“合成层优化”。
浏览器渲染管线的简化模型
浏览器渲染一帧(Frame)的主要步骤是:
- JavaScript 执行(更新 DOM / 样式属性)
- Style Calculation(计算样式)
- Layout / Reflow(计算元素位置)
- Paint(绘制像素)
- Composite(合成多个图层并送入 GPU 渲染)
不可见节点不会触发layout/Paint
只保留它们的几何信息(bounding box)在内存中
在van-swipe的实现中,只有当前页和相邻页的元素被设置了
transform: translateX(…),这些元素处于可见视口内,
不可见节点所在层被“裁剪”
Chrome / Safari / Edge 都使用 Compositing Layers(合成层)。
当元素通过 transform, will-change, 或动画被 GPU 加速时,
浏览器会把这个元素单独放在一个合成层(Texture)中。
van-swipe 整个容器就是一个单独的 layer。
它的子项 van-swipe-item会被当作该 layer 的子帧,只渲染当前 offset 对应的部分。
javaScript层面的开销几乎为零
vue的响应式系统有“引用复用机制”
在初始化时,quesitonList是一个大数组,每个元素是一个对象
当我们滑动时:
v-for=“opt in questions[index].opts”
只会访问当前索引对应的引用,vue不会再每次滑动时新建100个响应式对象
每次改变的都是index,如果key不变,则不做改变,如果key改变,则旧的DOM是不变的,新的DOM会复用旧的DOM并改变其中的值
例如:题目DOM如下,
<div class="question-box">
<h3>第 8 题</h3>
<p>这是第 8 题的内容</p>
<label><input type="radio">A</label>
<label><input type="radio">B</label>
</div>
当滑动到第9题时
发现旧 VNode 的根节点类型相同;
直接修改 h3的 textContent;
修改 p的textContent;
重绘被选中的 input状态;
其他 DOM 元素保持原位不动。
问题3:既然vue是复用,那为何在浏览器元素树中能看到100道题的DOM节点?
这涉及到了vue-vant-浏览器三个层面
你的数据
↓
Vue(模板编译、虚拟DOM diff)
↓
Vant(组合 Vue 子组件、封装交互逻辑)
↓
浏览器(渲染真实 DOM、绘制到屏幕)
vue层面
vue把v-for解析成虚拟DOM节点数组,然后diff+patch到真实DOM
<van-swipe>
<van-swipe-item v-for="q in questions" :key="q.id">
<div class="question">{{ q.title }}</div>
</van-swipe-item>
</van-swipe>
vant层面
vant是vue组件,有自己的模板和动画逻辑
<div class="van-swipe__track" style="transform: translateX(-offset)">
<slot></slot>
</div>
浏览器层
<div class="van-swipe">
<div class="van-swipe__track" style="transform: translateX(-300px)">
<div class="van-swipe-item">题1</div>
<div class="van-swipe-item">题2</div>
...
</div>
</div>
问题4:帧是什么?
-
在浏览器中,一帧(Frame)就是:
浏览器从执行 JavaScript → 计算布局 → 绘制像素,
再把这一画面显示到屏幕上的一个「画面周期」。 -
一帧 = 屏幕上显示的一张静态“图片”
-
动画(比如滑动、滚动、渐变)= 多帧连续显示
-
常见刷新率为 60Hz → 即 1 秒 60 帧。
1000ms ÷ 60 ≈ 16.67ms / 帧
浏览器必须在16.67ms中完成js执行,样式计算,布局,绘制,合成,不然就会掉帧 -
Chrome DevTools → Performance → Record
里面有每一帧的时间分配图(JS、Layout、Paint、Composite)。
或者用 FPS Meter 工具,在滑动时看帧率变化。 -
JS 执行太重(for 循环、复杂计算);DOM 更新太多(layout 大量触发);GPU 合成层太多(paint 时间太长);都会让帧率从 60fps 掉到 40fps、30fps 等。
1409

被折叠的 条评论
为什么被折叠?



