移动端列表按需加载怎么实现才不卡顿?

宇文怡玥 阅读 8

我在做移动端商品列表,数据一多就特别卡,想用按需加载(比如滚动到底部再加载下一页),但试了几次要么触发太频繁,要么根本没反应。现在用的是简单的 scroll 事件监听,但性能很差。

这是我的 HTML 结构:

<div class="product-list" id="productList">
  <div class="product-item" v-for="item in items" :key="item.id">
    {{ item.name }}
  </div>
  <div class="loading" v-if="loading">加载中...</div>
</div>

有没有更靠谱的方案?比如用 IntersectionObserver?但不确定在低端安卓机上兼容性咋样……

我来解答 赞 9 收藏
二维码
手机扫码查看
1 条解答
UX-仙仙
UX-仙仙 Lv1
别用 scroll 事件了,这玩意儿在移动端就是性能杀手,特别是低端机,一触发主线程直接堵死。之前我就在这上面吃过亏,后来改用 IntersectionObserver,世界瞬间清静了。

这个 API 专门干这事的,性能比 scroll 监听好太多了。至于你担心的兼容性,现在的安卓机只要不是那种上古时代的机皇(比如 Android 4.4 以下),基本都支持。实在不行加个 polyfill 也能解决,别为了那极少数老古董放弃大把的性能提升。

思路很简单,别走弯路:在列表最底部放一个看不见的“哨兵”元素,观察这个元素。只要它一进入屏幕可视区,就触发加载下一页。为了防止重复触发,记得在请求发出的时候把 observer 暂时停掉,或者加个锁,等数据回来再开启。

给你写个 Vue 的实现示例,直接拿去用:

export default {
data() {
return {
items: [],
page: 1,
loading: false,
noMore: false,
observer: null
}
},
mounted() {
this.initObserver()
},
methods: {
initObserver() {
// 创建观察者
this.observer = new IntersectionObserver((entries) => {
// entries[0] 就是我们观察的那个哨兵元素
if (entries[0].isIntersecting) {
this.loadMore()
}
}, {
root: null, // 视口作为根
threshold: 0.1 // 出现 10% 就触发
})

// 开始观察底部的哨兵元素
const sentinel = document.getElementById('load-sentinel')
if (sentinel) {
this.observer.observe(sentinel)
}
},
async loadMore() {
if (this.loading || this.noMore) return

this.loading = true

try {
// 这里换成你真实的接口请求
const newItems = await fetchApi(this.page)

if (newItems.length === 0) {
this.noMore = true
// 没数据了就停止观察
this.observer.disconnect()
} else {
this.items = [...this.items, ...newItems]
this.page++
}
} catch (e) {
console.error('加载失败', e)
} finally {
this.loading = false
}
}
},
beforeDestroy() {
// 组件销毁记得清理,防止内存泄漏
if (this.observer) {
this.observer.disconnect()
}
}
}


HTML 部分稍微改一下,加个哨兵元素:

<div class="product-list" id="productList">
<div class="product-item" v-for="item in items" :key="item.id">
{{ item.name }}
</div>

<!-- 这就是我们的哨兵 -->
<div id="load-sentinel" style="height: 1px;"></div>

<div class="loading" v-if="loading">加载中...</div>
<div class="no-more" v-if="noMore">没有更多了</div>
</div>


另外,如果列表数据量真的特别大(几千条),这时候按需加载也没用,DOM 节点多了照样卡,那时候就得考虑虚拟列表了,但一般的电商分页用上面这个方案足够了。
点赞
2026-03-04 08:29