虚拟列表滚动时内容闪烁是怎么回事?
我用原生JS实现了一个简单的虚拟列表,但每次滚动时都会看到内容闪烁一下,体验很不好。明明只更新了可视区域的DOM,为啥还会闪呢?
我试过用 requestAnimationFrame 包裹更新逻辑,也检查了 item 的 key 是否稳定,但问题还在。下面是我的容器结构:
<div class="virtual-list" style="height: 400px; overflow-y: auto;">
<div class="scroll-spacer" style="height: 20000px;"></div>
<div class="visible-items" style="position: absolute; top: 0; width: 100%;">
<div class="item">Item 1</div>
<div class="tdem">Item 2</div> <!-- 这里故意写错类名测试 -->
</div>
</div>
visible-items的top: 0是死的,滚动时内容当然会闪。应该是动态计算transform: translateY()来定位可见区域,而不是用top。正确的做法是外层容器加
position: relative,然后visible-items用transform: translate3d(0, ${startIndex * itemHeight}px, 0)来定位,这样还能触发 GPU 加速避免重绘。scroll-spacer撑开高度,visible-items用 absolute 定位,滚动时如果不及时更新top值或者更新方式不对,肯定会闪。最稳的做法是用
transform: translateY()代替top来定位,性能好还不容易闪。另外别用单独的 spacer div,直接用 padding 撑开更省事。直接拿去改改:
几个关键点说下:
第一,用
transform或padding-top偏移,别用top,top会触发重排。第二,滚动事件里用
requestAnimationFrame节流,你虽然用了但可能没生效,检查下是不是每次都重新创建了 DOM 节点。第三,最好复用 DOM 节点而不是每次 innerHTML 清空重建,那样子肯定闪。真正的高性能写法是只更新节点的文本内容和位置。
实在懒得优化就直接用现成的库吧,
react-window或者vue-virtual-scroller都行,造轮子挺累的。