虚拟列表滚动时内容闪烁是怎么回事?

司徒东昇 阅读 6

我用原生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>
我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
UP主~宇杰
你这结构有问题,visible-itemstop: 0 是死的,滚动时内容当然会闪。应该是动态计算 transform: translateY() 来定位可见区域,而不是用 top

正确的做法是外层容器加 position: relative,然后 visible-itemstransform: translate3d(0, ${startIndex * itemHeight}px, 0) 来定位,这样还能触发 GPU 加速避免重绘。






// 滚动时更新位置
const offset = startIndex * itemHeight;
visibleItems.style.transform = translate3d(0, ${offset}px, 0);
点赞 1
2026-03-01 22:14
极客瑞芹
看了你的结构,问题大概率出在 absolute 定位的更新时机上。你用 scroll-spacer 撑开高度,visible-items 用 absolute 定位,滚动时如果不及时更新 top 值或者更新方式不对,肯定会闪。

最稳的做法是用 transform: translateY() 代替 top 来定位,性能好还不容易闪。另外别用单独的 spacer div,直接用 padding 撑开更省事。

直接拿去改改:

<div class="virtual-list" style="height: 400px; overflow-y: auto; position: relative;">
<div class="content-wrapper" style="padding-top: 0; box-sizing: border-box;">
<!-- 可视区域的item直接渲染在这里 -->
</div>
</div>


const container = document.querySelector('.virtual-list');
const wrapper = container.querySelector('.content-wrapper');

const itemHeight = 50;
const totalItems = 10000;
const visibleCount = Math.ceil(400 / itemHeight) + 2; // 多渲染2个缓冲

let startIndex = 0;

// 用padding撑开滚动区域
wrapper.style.paddingTop = '0';
wrapper.style.height = totalItems * itemHeight + 'px';

function render() {
const scrollTop = container.scrollTop;
const newStartIndex = Math.floor(scrollTop / itemHeight);

if (newStartIndex === startIndex) return;
startIndex = newStartIndex;

const endIndex = Math.min(startIndex + visibleCount, totalItems);

// 关键:用transform定位,不要清空DOM重绘
const offsetY = startIndex * itemHeight;

let html = '';
for (let i = startIndex; i < endIndex; i++) {
html += <div class="item" style="height: ${itemHeight}px; line-height: ${itemHeight}px;">Item ${i + 1}</div>;
}

// 用transform偏移,比top性能好
wrapper.innerHTML = html;
wrapper.style.transform = translateY(${offsetY}px);
wrapper.style.paddingTop = offsetY + 'px';
}

container.addEventListener('scroll', () => {
requestAnimationFrame(render);
});

render();


几个关键点说下:

第一,用 transformpadding-top 偏移,别用 toptop 会触发重排。

第二,滚动事件里用 requestAnimationFrame 节流,你虽然用了但可能没生效,检查下是不是每次都重新创建了 DOM 节点。

第三,最好复用 DOM 节点而不是每次 innerHTML 清空重建,那样子肯定闪。真正的高性能写法是只更新节点的文本内容和位置。

实在懒得优化就直接用现成的库吧,react-window 或者 vue-virtual-scroller 都行,造轮子挺累的。
点赞
2026-03-01 14:07