2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue 3 SFCとIntersection Observer APIでエンドレスのデータストリームを

Posted at

目的

Vue 3のSFCでIntersection Observer APIを使って、ページの最後にスクロールした際に、情報取得を自動で行う仕組みを作る

目次

  1. 背景
  2. Observer部品を作る
  3. List部品を作る
  4. エンドレスにする
  5. まとめ

背景

Intersection Observer APIを従来のVueで使う方法を紹介する記事が多いが、新しいVue 3 SFCでの使い方が少し違うので、自分の解決方法を紹介しようと思います。
Alex Moralesのこの記事、そしてMDNのドキュメントも参考にしています。

Observer部品を作る

エンドレスのリストを作るために、ページの最後までスクロールした時にイベントが起きて、データが取得されなければならない。
ページの最後までスクロールした時に、Eventが発生すれば、データをサーバーから取得して、表示のリストにPushすればいいです。
なので、そのEventを発生させる部品を作ります。

Oberserver.vue
<script setup lang="ts">
import { onMounted, ref } from "vue";

const emit = defineEmits<{ (e: "page-end"): void }>();

const end = ref<HTMLDivElement>();

onMounted(() => {
  const observer = new IntersectionObserver(
    (entries) => {
      const firstEntry = entries[0];
      if (firstEntry.isIntersecting) {
        console.log(firstEntry);
        emit("page-end");
      }
    },
    {
      root: document,
      threshold: 0,
      rootMargin: "0px",
    }
  );
  observer.observe(end.value);
});
</script>

<template>
  <div ref="end" />
</template>

Intersection Observerを作る時に、引数として、callback関数と、設定値のオブジェクトを渡します。

callback関数ですが、Oberserverが観察しているElementが見えてきた時に実行されます。
そして、登録されていて尚且つ見えている全てのElementが引数にArrayとして渡されるので、Arrayを解梱さんだれーならん。

Arrayの中身には、登録されたElementについて様々な情報が入っていますが、今回気になっているのは、isIntersecting(画面に差し掛かって、入ってきているかどうか)のbooleanです。

(entries) => {
      const firstEntry = entries[0];
      if (firstEntry.isIntersecting) {
        console.log(firstEntry);
        emit("page-end");
      }
    }

差し掛かっていれば、Vue内で"page-end"というEventを発生させましょう!

設定値のオブジェクトですが、

  1. root: Observerが見る枠(今回はページ全体でObserverに見てほしいのでdocumentを渡す)
  2. threshold: 対象物のElementがどれくらい見えていればcallback関数を実行するかを設定する(0で1ピクセルでも見えれば実行される)
  3. rootMargin: Observerが見る枠を調整する("xxpx"で指定する)
{
   root: document,
   threshold: 0,
   rootMargin: "0px",
}

また、もう一つ工夫があります。

observerのオブジェクトを作った後、観察する対象物を登録しなければならない。
Vue 3のSFCでは、refを使ってDOMにアクセスすることがベストなので、endというrefを作って、それを登録します。

List部品を作る

List部品には https://jsonplaceholder.typicode.com/comments のAPIを使います。

stateではページ目と、表示されるコメント情報を保持する。

fetchDataの最後に、取得したデータをstateに保存する時ですが、state内の既存のArrayを解梱した上で、取得したArrayも解梱して新しいArrayにしてから上書きします。

これで50個分のコメントを読み込んで表示されます。

EndlessList.vue
<script setup lang="ts">
import { onMounted, reactive } from "vue";

const state = reactive<{
  page: number;
  data: {
    postId: number;
    id: number;
    name: string;
    email: string;
    body: string;
  }[];
}>({ page: 1, data: [] });

const fetchData = async () => {
  const results = await fetch(
    `https://jsonplaceholder.typicode.com/comments?_page=${state.page}&_limit=50`
  );

  const json = await results.json();

  state.data = [...state.data, ...json];
};

onMounted(async () => fetchData());

</script>

<template>
  <div class="card" v-for="comment in state.data" :key="comment.id">
    <h1>{{ comment.name }}</h1>
    <p>{{ comment.body }}</p>
  </div>
</template>

<style>
.card {
  width: 90%;
  padding: 1rem;
  margin: 1rem auto 0 auto;
  background-color: white;
  border: 1px solid white;
  border-radius: 8px;
  box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.226);
}
</style>

エンドレスにする

上記に作ったObserver.vueの出番が来ました。

先ほど作ったリストの最後にObserver部品を入れれば、ページの最後までスクロールした時にEventが発生します。

余談ですが、v-if="state.data.length"を追加しているのは、最初のデータが読み込まれるまで、Observer部品が見えてしまうので、リストが表示されるまで出さないでおきます。

EndlessList.vue
<script setup lang="ts">
import { onMounted, reactive } from "vue";
import Observer from "./Observer.vue";
import BaseCard from "./BaseCard.vue";

const state = reactive<{
  page: number;
  data: {
    postId: number;
    id: number;
    name: string;
    email: string;
    body: string;
  }[];
}>({ page: 1, data: [] });

const fetchData = async () => {
  const results = await fetch(
    `https://jsonplaceholder.typicode.com/comments?_page=${state.page}&_limit=50`
  );

  const json = await results.json();

  state.data = [...state.data, ...json];
};

onMounted(async () => fetchData());
</script>

<template>
  <div class="card" v-for="comment in state.data" :key="comment.id">
    <h1>{{ comment.name }}</h1>
    <p>{{ comment.body }}</p>
  </div>
  <Observer v-if="state.data.length" />
</template>

このままページの最後までスクロールしてみると、
スクリーンショット 2022-02-08 16.10.18.png
Eventが発生して、consoleに無事にログされます!

ここで、fetchDataをもう一度実行すればいいわけです。

EndlessList.vue
<script setup lang="ts">
import { onMounted, reactive } from "vue";
import Observer from "./Observer.vue";

const state = reactive<{
  page: number;
  data: {
    postId: number;
    id: number;
    name: string;
    email: string;
    body: string;
  }[];
}>({ page: 1, data: [] });

const fetchData = async () => {
  const results = await fetch(
    `https://jsonplaceholder.typicode.com/comments?_page=${state.page}&_limit=50`
  );

  const json = await results.json();

  state.data = [...state.data, ...json];
};

onMounted(async () => fetchData());

const loadMorePosts = () => {
  state.page++;
  fetchData();
};
</script>

<template>
  <div class="card" v-for="comment in state.data" :key="comment.id">
    <h1>{{ comment.name }}</h1>
    <p>{{ comment.body }}</p>
  </div>
  <Observer v-if="state.data.length" @page-end="loadMorePosts" />
</template>

state.page++でページのメモリーも増やさんだれーならんどー!

まとめ

これでIntersection Observerを使って簡単にエンドレスのリストを実装しました。本当は、Intersection Observerにはこれ以外に色々な使い方があって、もっと追求すれば、できることが増えると思います!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?