🐈

VueUseのuseIntersectionObserverで無限スクロールを実装する

2024/01/24に公開

はじめに

この記事について

VueUse のuseIntersectionObserverフックについて、実装しながら学べます。
VueUse を使ったことないけれど使ってみたいという方は、ぜひ自分で書いてみて試してください!

VueUse の useIntersectionObserver について

そもそも VueUse とは?

VueUse は「Collection of Vue Composition Utilities」と公式に書かれている通り、Vue.js を使う上で強力な Helper 関数群になります。
様々な Helper を Composition API の形式で提供しており、Vue.js での開発がより便利になります。

詳しくは公式のドキュメントを参照してください。

https://vueuse.org/

VueUse の useIntersectionObserver の紹介

この記事では、VueUse ライブラリの中でも特に useIntersectionObserver フックに焦点をあてています。

https://vueuse.org/core/useIntersectionObserver/#useintersectionobserver

このフックについての公式の説明が

Detects that a target element's visibility.(対象要素の可視性を検出する)

だけで恐ろしくシンプルです。ただ、ページ内のデモを見ればなんとなくわかるはずです。

useIntersectionObserverDemo

ターゲットの要素の出現を検知して、特定の処理を実行するフックになっています。
上の例だと、Hello world!という文字列をターゲットとして、スクロール範囲に達した時だけ、Element outside the viewport(黄)からElement inside the viewport(緑)に切り替わっていますね。

以降の内容は、簡単な Web アプリを通して useIntersectionObserver で無限スクロールを実現してみます。

備考

必要な Vue.js の機能(ref, onMounted)の説明は冗長になるため割愛します。
またコードは<script setup>を使用した記述になっています。これらは Vue.js 公式ドキュメントに詳細が載っています!

https://ja.vuejs.org/

また、VueUse が提供している Playground 上での実装を想定しています。

https://play.vueuse.org/

VueUse を使ってみよう

作ってみるアプリ概要

スクロールすると無限に猫ちゃんが閲覧できる Web アプリです。
API は学習用に利用できるThe Cat API - Public APIsの API を使用しました。 [1]

実際のアプリは以下のイメージです 🐈

useIntersectionObserver

このアプリは VueUse が提供している Playground 上ですぐお試しできます 👌

コードの詳細

Script 部分

useIntersectionObserver のインポート

公式を参考に今回使いたいuseIntersectionObserverをインポートします。以降の解説は<script setup>内のコードだと思ってください。今回はコード自体も短いのでApp.vueに直接記載しています。

// App.vue
<script setup lang="ts">
import { useIntersectionObserver } from "@vueuse/core";
</script>

https://vueuse.org/guide/#usage-example

関数を定義

今回使用する関数は以下の 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 の一つです。

https://developer.mozilla.org/ja/docs/Web/API/IntersectionObserverEntry

発火タイミングは以下の図のイメージです。
IntersectionObserverEntry
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、様々なフックが提供されているので、今後も利用方法を紹介できればと思います!

参考記事

https://qiita.com/tryforth/items/dc122774241ed7e6f2fa

https://itosae.com/archives/467

脚注
  1. API の制限(リクエスト数の上限など)に引っかからないように節度を持ってご利用ください。 ↩︎

  2. https://ja.vuejs.org/guide/essentials/template-refs.html#template-refs ↩︎ ↩︎

GitHubで編集を提案
Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion