VueUseのuseIntersectionObserverで無限スクロールを実装する
はじめに
この記事について
VueUse のuseIntersectionObserver
フックについて、実装しながら学べます。
VueUse を使ったことないけれど使ってみたいという方は、ぜひ自分で書いてみて試してください!
VueUse の useIntersectionObserver について
そもそも VueUse とは?
VueUse は「Collection of Vue Composition Utilities」と公式に書かれている通り、Vue.js を使う上で強力な Helper 関数群になります。
様々な Helper を Composition API の形式で提供しており、Vue.js での開発がより便利になります。
詳しくは公式のドキュメントを参照してください。
VueUse の useIntersectionObserver の紹介
この記事では、VueUse ライブラリの中でも特に useIntersectionObserver
フックに焦点をあてています。
このフックについての公式の説明が
Detects that a target element's visibility.(対象要素の可視性を検出する)
だけで恐ろしくシンプルです。ただ、ページ内のデモを見ればなんとなくわかるはずです。
ターゲットの要素の出現を検知して、特定の処理を実行するフックになっています。
上の例だと、Hello world!
という文字列をターゲットとして、スクロール範囲に達した時だけ、Element outside the viewport
(黄)からElement inside the viewport
(緑)に切り替わっていますね。
以降の内容は、簡単な Web アプリを通して useIntersectionObserver
で無限スクロールを実現してみます。
備考
必要な Vue.js の機能(ref
, onMounted
)の説明は冗長になるため割愛します。
またコードは<script setup>
を使用した記述になっています。これらは Vue.js 公式ドキュメントに詳細が載っています!
また、VueUse が提供している Playground 上での実装を想定しています。
VueUse を使ってみよう
作ってみるアプリ概要
スクロールすると無限に猫ちゃんが閲覧できる Web アプリです。
API は学習用に利用できるThe Cat API - Public APIsの API を使用しました。 [1]
実際のアプリは以下のイメージです 🐈
このアプリは VueUse が提供している Playground 上ですぐお試しできます 👌
コードの詳細
Script 部分
useIntersectionObserver のインポート
公式を参考に今回使いたいuseIntersectionObserver
をインポートします。以降の解説は<script setup>
内のコードだと思ってください。今回はコード自体も短いのでApp.vue
に直接記載しています。
// App.vue
<script setup lang="ts">
import { useIntersectionObserver } from "@vueuse/core";
</script>
関数を定義
今回使用する関数は以下の 3 つです。
// .
import { ref } from "vue"; // 追加
const visibleImages = ref([]); //猫ちゃんの画像が保存される(スクロールすると追加される)
const observerElement = ref(null); // 画像読み込みのトリガーとなる要素を参照させる
const isLoading = ref(false); // ロード中を表すフラグ
observerElement
が抽象的で難しいと思いますが、「監視のポイント」みたいなものだと思ってください。これを Template 内の要素に紐づけることでスクロールによる関数実行を実現します。
useIntersectionObserver を利用する
useIntersectionObserver を実行します。以下でそれぞれの引数について説明します。
// .
useIntersectionObserver(
observerElement, // ①監視対象の要素
([entry]) => {
{
if (entry.isIntersecting) {
loadMoreImages(); // TODO
}
} // ②監視対象の要素に変更があった時に呼び出される関数
},
{ threshold: 0.5 } // ③オプション(対象が表示されている割合)
);
① 監視対象の要素
ターゲットの対象の要素を指定します。今回は template 内での要素からテンプレート参照を行い監視対象を決めています。[2]
② 監視対象の要素に変更があった時に呼び出される関数
引数は、監視対象が別の要素に交差した際に発火されるオブジェクトIntersectionObserverEntry
になります。
IntersectionObserverEntry
はブラウザが提供している API の一つです。
発火タイミングは以下の図のイメージです。
entry.isIntersecting
にて交差したかどうかを判断できるので今回は、true
の場合は読み込み処理を行っています。
③ オプション(対象が表示されている割合)
以下の3つの要素により、動作をカスタマイズできます。
今回の場合は、threshold: 0.5
として、対象が半分表示された時に ② の関数が呼び出すようにしています。
root
: 監視の基準となる要素を指定します。rootMargin
:root
要素の周囲のマージンを設定します。threshold
: どの程度の割合で要素が表示されているときにコールバックを呼び出すかを指定する数値または数値の配列。この例では、1.0
が設定されており、対象の要素が 100%表示されたときにのみコールバックが呼び出されます。
The Cat API の利用
猫ちゃん画像を取得する関数を作成を作成します。
やっていることは単純でThe Cat APIのデータを取得しています。
取得中はisLoading
フラグをtrue
にしてあげてロード中であることを画面に描画します。
またマウント時にloadMoreImages
を実行してあげてるようにしています。
// .
import { ref, onMounted } from "vue"; // 変更
const loadMoreImages = async () => {
isLoading.value = true;
const response = await fetch("https://api.thecatapi.com/v1/images/search");
const cats = await response.json();
visibleImages.value.push(cats[0].url);
isLoading.value = false;
};
onMounted(() => {
loadMoreImages(); // マウント時処理を追加
});
Template 部分
v-for="image in visibleImages
にて、取得した画像を全て表示しています。
また、ref="observerElement"
で要素を Script 部分から参照できるように紐づけています。[2:1]
その要素(今回でいうとdiv
)を今回はuseIntersectionObserver のターゲットとしています。
最後に Tailwind.css を使って、若干のスタイリングを施しています。
<template>
<div id="app" class="container mx-auto">
<div v-for="image in visibleImages" :key="image" class="mb-4">
<img :src="image" class="w-full object-cover" />
</div>
<div ref="observerElement" class="text-center py-4">
<p v-if="loading">Loading...</p>
</div>
</div>
</template>
実装コード
出来上がったコードはこちらを参照してください。
VueUse を使ったことでシンプルに実装できていますね!!
実装コード全体
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useIntersectionObserver } from "@vueuse/core";
const visibleImages = ref([]);
const observerElement = ref(null);
const loading = ref(false);
useIntersectionObserver(
observerElement,
([entry]) => {
{
if (entry.isIntersecting) {
loadMoreImages();
}
}
},
{ threshold: 0.5 }
);
const loadMoreImages = async () => {
loading.value = true;
const response = await fetch("https://api.thecatapi.com/v1/images/search");
const cats = await response.json();
visibleImages.value.push(cats[0].url);
loading.value = false;
};
onMounted(() => {
loadMoreImages();
});
</script>
<template>
<div id="app" class="container mx-auto">
<div v-for="image in visibleImages" :key="image" class="mb-4">
<img :src="image" class="w-full object-cover" />
</div>
<div ref="observerElement" class="text-center py-4">
<p v-if="loading">Loading...</p>
</div>
</div>
</template>
おわりに
以上でuseIntersectionObserver
の紹介は終わりです。
VueUse、様々なフックが提供されているので、今後も利用方法を紹介できればと思います!
参考記事
-
API の制限(リクエスト数の上限など)に引っかからないように節度を持ってご利用ください。 ↩︎
-
https://ja.vuejs.org/guide/essentials/template-refs.html#template-refs ↩︎ ↩︎
Discussion