Vue.jsでリスト表示を行う際、スクロールの位置に応じて次のデータを読み込む「無限スクロール(Infinite Scroll)」は、ユーザー体験を向上させる便利なUI手法です。従来はスクロールイベントを手動で監視する方法が一般的でしたが、Intersection Observer APIを使えば、より簡潔で高性能な実装が可能になります。本記事では、Vue 3とComposition APIで無限スクロールを実装する方法を解説します。
実装の概要
無限スクロールは以下のような流れで実装します:
- 表示用リストとデータ取得関数を用意
- リスト末尾に「監視用ダミー要素」を配置
- Intersection Observerでその要素の表示を検知
- 表示されたら次のデータを非同期で読み込む
Vueコンポーネントのサンプル実装
<template>
<div class="post-list">
<div v-for="post in posts" :key="post.id" class="post-item">
{{ post.title }}
</div>
<div ref="loadTrigger" class="load-trigger">読み込み中...</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const posts = ref([])
const page = ref(1)
const isLoading = ref(false)
const loadTrigger = ref(null)
let observer = null
const fetchPosts = async () => {
if (isLoading.value) return
isLoading.value = true
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page.value}`)
const data = await response.json()
posts.value.push(...data)
page.value++
isLoading.value = false
}
onMounted(() => {
observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
fetchPosts()
}
},
{
threshold: 1.0
}
)
if (loadTrigger.value) {
observer.observe(loadTrigger.value)
}
})
onUnmounted(() => {
if (observer && loadTrigger.value) {
observer.unobserve(loadTrigger.value)
}
})
</script>
<style scoped>
.post-list {
max-width: 600px;
margin: 0 auto;
}
.post-item {
padding: 16px;
border-bottom: 1px solid #ccc;
}
.load-trigger {
padding: 16px;
text-align: center;
color: #888;
}
</style>
Intersection Observerの利点
- スクロールイベントを使わないためパフォーマンスが高い
- 監視対象のDOMが画面内に入ったかを自動検知
- 複雑な計算やポーリングが不要
注意点とベストプラクティス
- 読み込みが完了する前に複数リクエストを投げないように
isLoading
で制御 - 全データを取得しきったら監視を停止(例:取得件数が0になったら)
- 表示エリアが小さいとすぐに読み込みが連発することがあるため、初期表示件数を調整
まとめ
Vue.jsで無限スクロールを実装するには、Intersection Observerを使うのが最も効率的です。従来のスクロールイベントよりも可読性・保守性に優れ、パフォーマンス面でも有利です。表示するリストが大量になるアプリや、ユーザーがコンテンツをスムーズに閲覧できるUIを構築したいときに、ぜひ活用してみてください。