虚拟列表滚动时内容错位,如何解决?
我在给聊天列表做虚拟滚动优化时遇到了问题,当快速滚动到中间区域后,列表内容会出现几秒的错位闪烁,但过一会又恢复正常。
已经用react-window实现了基础虚拟列表,设置了itemSize为60,容器高度500px。尝试在scroll时手动计算offset:
const [offset, setOffset] = useState(0);
const handleScroll = (e) => {
const scrollTop = e.target.scrollTop;
setOffset(scrollTop % 60); // 按item高度取余
};
但发现当聊天消息包含图片时,实际item高度会超过60px,导致滚动位置计算错误。控制台没有报错,但视觉上出现内容错位和闪动,特别是滚动到包含长文本的item时更明显。
试过把itemSize改成变量动态计算,但这样会导致渲染区域不断变化,反而更卡顿。请问这种动态高度的虚拟列表该怎么处理?
DynamicSizeList 需要你提供一个
size函数,告诉它每个 item 的真实高度。但关键点是:这个高度不能每次 scroll 都实时测(不然卡死),得提前缓存好,或者用异步预渲染。比如你可以这样初始化:
注意几个坑:
1.
measureRow里的 ref 不要直接加在虚拟列表的子组件上,得用itemData传进去,否则 ref 拿不到真实 DOM2. 如果消息量很大,建议用防抖+异步更新缓存,不然频繁写 localStorage 会卡
3. 有些同学会用 ResizeObserver 监听 item 变化,但聊天列表里图片加载是异步的,等图片加载完高度才变,所以最好在图片
onLoad后手动触发一次缓存更新如果不想搞这么复杂,也可以退而求其次:给所有消息设个最大高度,比如
max-height: 120px; overflow: hidden;,再加个「展开」按钮。这样还是能用 FixedSizeList,滚动丝滑,虽然少了点灵活性,但开发成本低很多。react-virtualized的AutoSizer和CellMeasurer。核心思路就是让每个 item 的高度自己去测量,而不是手动指定固定高度。这样即使有图片或者长文本,也能正确计算高度。
简单贴个代码示例:
关键点说一下:
1.
CellMeasurerCache是用来缓存每个 item 的高度,避免重复测量。2.
CellMeasurer包裹你的 item,会自动测量实际渲染高度。3.
AutoSizer让容器能自适应宽度和高度。性能上不用担心,
react-virtualized做得比react-window更成熟,尤其是动态高度场景下更稳定。我自己用了这个方案后,滚动体验提升非常明显,再也不会出现错位闪烁的问题。最后提醒一句,别再去折腾手动计算 offset 或者动态修改 itemSize,太容易出问题了,相信我,我都试过,不值得。