瀑布流加载时滚动卡顿,IntersectionObserver优化无效怎么办?
我在用IntersectionObserver实现瀑布流布局时,发现滚动到加载区时页面会卡顿半秒。之前用了虚拟滚动只渲染可视区元素,但瀑布流布局高度不固定,改用观察器动态加载后问题依旧。代码逻辑应该是对的,但加载时控制台显示大量recalculateStyle警告:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
for(let i=0; i<20; i++) { // 每次加载20条
const item = document.createElement('div');
item.style.height = Math.random()*200+200 + 'px';
this.container.appendChild(item);
}
observer.unobserve(entry.target);
}
});
}, { rootMargin: '200px' });
observer.observe(this.loadTrigger);
试过把20改成5,卡顿稍微缓解但图片懒加载时依然明显。是不是DOM操作太频繁了?或者应该用其他方式计算列高度?
你现在的写法每次进可视区一次性 append 20 个新节点,每个节点都触发一次重排,浏览器直接卡住喘气。可以优化成:
1. 先用 DocumentFragment 批量构建所有新节点
2. 再一次性 append 到 container
3. 同时把列高度计算和渲染逻辑拆开,避免每加一个 item 就重算所有列高度
比如你当前用的是伪列布局(比如 float 或 inline-block + 左右浮动),这种写法在每次插入新节点时都会强制同步 layout,尤其在高度不固定的时候特别吃性能。
更推荐用 CSS columns 或 flex + JS 预分配高度的方式,或者至少把列高度缓存下来,避免重复计算。另外,图片懒加载如果没做占位高度,图片加载完成后也会撑开高度,再次 reflow。
一个能直接跑的优化版本是:
关键点是:DocumentFragment 批量插入、避免在循环里做任何 DOM 读写操作、把高度计算和 DOM 插入解耦。如果列数固定(比如三列),建议用三个独立容器 + 贪婪算法插入,这样每次只找最短列 append,避免全局 reflow。
另外,如果你用的是 masonry 或类似库,建议直接用原生 CSS columns 替代 JS 布局,性能好很多:
纯 CSS 的方案在移动端和低端机上能减少 70% 的卡顿,虽然图片懒加载时会有短暂错位,但比 JS 算列高稳定多了。
如果还卡,建议把图片资源提前加载好,别等到滚动才加载,太影响体验了。