【Vue 3】で無限スクロールを実装

下まで行ったら読み込んで次のページを表示・・・と所謂無限スクロールを実装しようとした際に意外と高さを取得したりなんだりと詰まってしまったのですが、できると割と簡単に作れるのでその紹介です。

前提

スクロールで下まで行った時に非同期で叩いたAPIの結果を表示、というのがよくあると思いますが、今回は簡略化のために長いリストの一部を1ページとして表示する様にします。

1ページに 1~10 の行を表示する、ただそれだけの画面です。

出来上がるとこの様になります。

左に出ているのが現在表示されているページ数です。

下までスクロールされて次のページが読み込まれるとページ数がカウントされます。

表示しているのは以下の様なただのリストです。

    const allLsit = ref([1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10])
    const displayList = ref([])

    const scrollComponent = ref(null)
    const pageNum =ref(1)
    const rowsPerPage = 10 

    displayList.value = allLsit.value.slice(0 , (pageNum.value * rowsPerPage))

template

template は以下の様になります。

<template>
  <div class="page">
    {{pageNum}}
  </div>
  <div class="list" ref='scrollComponent'>
    <div v-for="list in displayList" :key="list">
      <div class="row">
        <h1>{{list}}</h1>
      </div>
    </div>
  </div>  
</template>

ref で指定した scrollComponent の底の高さが innerHeight より小さい値がどうかで下まで来たかを判定する様にします。

script

onMounted で EventListener に scroll を追加して スクロール時に handleScroll を実行する様にします。


  onMounted(() => {
		window.addEventListener("scroll", handleScroll)
	})

	onUnmounted(() => {
		window.removeEventListener("scroll", handleScroll)
	})

    const handleScroll = () => {
      if (allLsit.value.length === displayList.value.length) return
      let element = scrollComponent.value
    if ( element.getBoundingClientRect().bottom < window.innerHeight ) {
        displayList.value = allLsit.value.slice(0 , (pageNum.value * rowsPerPage) + rowsPerPage)
				pageNum.value += 1
			}
    }

if ( element.getBoundingClientRect().bottom < window.innerHeight ) ここの判定以下が下までスクロールした際に実行する処理になります。

実行

上記を実装すると以下の様になります。

以下にこちらのページの全体を載せておきます。

<template>
  <div class="page">
    {{pageNum}}
  </div>
  <div class="list" ref='scrollComponent'>
    <div v-for="list in displayList" :key="list">
      <div class="row">
        <h1>{{list}}</h1>
      </div>
    </div>
  </div>  
</template>

<script>
import { ref, onMounted, onUnmounted} from 'vue'
export default {
  name: 'ScrollList',
  setup() {

    const allLsit = ref([1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10])
    const displayList = ref([])

    const scrollComponent = ref(null)
    const pageNum = ref(1)
    const rowsPerPage = 10 

    displayList.value = allLsit.value.slice(0 , (pageNum.value * rowsPerPage))

  onMounted(() => {
		window.addEventListener("scroll", handleScroll)
	})

	onUnmounted(() => {
		window.removeEventListener("scroll", handleScroll)
	})

    const handleScroll = () => {
      if (allLsit.value.length === displayList.value.length) return
      let element = scrollComponent.value
      if ( element.getBoundingClientRect().bottom < window.innerHeight ) {
        displayList.value = allLsit.value.slice(0 , (pageNum.value * rowsPerPage) + rowsPerPage)
				pageNum.value += 1
			}
    }

    return {
      allLsit,
      displayList,
      scrollComponent,
      pageNum
    }
  }
}
</script>

<style scoped>
.row {
  border: groove;
  height: 10vh;
}

.page {
  position: fixed;
  background-color: bisque;
  color: aquamarine;
  border: groove;
  width: 10vw;
  height: 10vw;
  font-size: 10vw;
  padding: 10px;
}
</style>

参考書籍

まだ若干 3 の方は情報が少ない気もするので本を読んで体系的に勉強したいですが Vue 3 対応している日本語の本はこれくらいでしょうか。。?

下記は自分が読んだこともあるので紹介しておきます。