お問い合わせ

ブログ

これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。

vue3 composition apiでIntersectionObserverを使って無限スクロール(infinity loading)を作る!

okuda Okuda 2 years

vue3 composition apiで「無限スクロール」を作る!

Intersection Observer API を使用して「無限スクロール」を実装。
Intersection Observer API を使うことでパフォーマンスの問題を解消。

オブザーバの準備

IntersectionObserver オブジェクトを作成し、交差を監視したい要素をobserveするには以下のように行う。
IntersectionObserver の引数には callbackoption を渡す。 パラメータには監視するエレメント IntersectionObserverEntry が全て渡される。

IntersectionObserverEntry には以下のようなプロパティが含まれる。

  • entry.intersectionRatio: プロパティは交差しているかどうかのブール値
  • entry.intersectionRatio: 交差している量を0.0〜1.0の範囲で表示
  • entry.intersectionRect): 交差している領域の矩形オブジェクト
const callback = async (entries) => {
    for (const entry of entries) {
        console.log(entry);
        console.log(entry.isIntersecting);
        console.log(entry.intersectionRatio);
        console.log(entry.intersectionRect);
    }
}

const options = {
    // rootは交差判定のベースとなる要素を指定、 デフォルトでは`viewport`
    root: document.querySelector('.root'),

    // rootMarginはrootからのマージンを指定、
    // rootと交差する前に発火させることがでる
    // `px`と`%`が使用可能、デフォルトは’0px 0px 0px 0px’
    rootMargin: '5%',

    // コールバック関数が呼ばれるタイミングを指定
    // 0 = rootに入ってきたとき, 1 = 完全にrootに入ってきたとき
    // threshold: [0, 1]のように配列で渡すと0と1で2回発火する
    threshold: 0
}

// オブザーバの作成
const observer = new IntersectionObserver(callback, options);

// 要素をobserve
const target = document.querySelector('.target')
observer.observe(target);

vuejsで実装する

無限スクロールようにコンポネントを作成して、各ページコンポネントで使用するように実装する。

無限スクロールコンポネントを作成

<template>

    <!-- 取得したアイテムを表示するスロット -->
    <slot name="items"></slot>

    <!-- 監視するエレメント -->
    <div class="infinity" ref="observeElement">

        <!-- ローディング中に表示するエレメント -->
        <span v-show="loading" class="infinity__loading">
            <slot name="loading">loading...</slot>
        </span>

        <!-- すべてロードしあたときに表示するエレメント -->
        <span v-show="isLoaded" class="infinity__loaded">
            <slot name="loaded">loaded!</slot>
        </span>
    </div>

</template>

<script>

import {ref, onMounted} from "vue"
export default {

    name: "UiInfinity",
    props: {
        // 発火したときに実効するファンクション
        callback: {
            type: Function,
            required: false,
        },
        // すべてロード完了はパレントからもらう
        isLoaded: {
            type: Boolean,
            default: false,
        },
    },
    // 発火後にレスポンスを返すイベント
    emits: ['fetched'],
    setup(props, context)
    {
        // ロード中
        const loading = ref(false)
        // refで要素を取得
        const observeElement = ref(null)

        // entries = 監視するエレメントすべて
        const callbackWrapper = async (entries) =>
        {
            // ここでは1つなので最初の配列を指定
            const entry = entries[0];

            // entry.isIntersecting = プロパティは交差しているかどうかのブール値
            // パレントからもらった `props.isLoaded` が `false` のとき
            if (entry && entry.isIntersecting && !props.isLoaded) {

                console.log('observe!')
                console.log(entry.intersectionRatio); // 交差している量を0.0〜1.0の範囲で表示
                console.log(entry.intersectionRect); // 交差している領域の矩形オブジェクト

                // ローディング開始
                loading.value = true;

                // ファンクション実行
                const {data, status} = await props.callback();

                // ローディング終了
                loading.value = false;

                // if OK return
                if (status === 200) {
                    context.emit('fetched', data.content)
                }
            }
        }

        const options = {
            // 1 = 完全にrootに入ってきたとき
            threshold: 0
        }

        // `onMounted`内で監視する要素を取得
        onMounted(() =>
        {
            // IntersectionObserverオブジェクトを作成
            const observer = new IntersectionObserver(callbackWrapper, options);

            // 監視する要素をobserve
            observer.observe(observeElement.value);
        })

        return {
            loading,
            // リターンしないと取れない
            observeElement,
        }
    }

}; 
</script>

<style scoped>
.infinity {
    min-height: 1px;
    background-color: tomato;
    display: flex;
    justify-content: center;

}
</style>

無限スクロールコンポネントを使用する

<template>
    <div class="page">

        <div>total tour: {{items.length}} last page: {{lastPage}} current page: {{page}}</div>

        <!-- 無限スクロールコンポネント -->
        <UiInfinity :callback="fetchEntries" @fetched="pushItems" :is-loaded="loaded">
            <template #items>
                <ul v-if="item" class="items">
                    <li v-for="item of items" :key="item.id">
                        {{item.id}}: {{item.name}}
                    </li>
                </ul>
            </template>
            <template #loading> --- loading... --- </template>
            <template #loaded>--- loaded! ---</template>
        </UiInfinity>

    </div>
</template>

<script>
import UiInfinity from "@/components/UiInfinity.vue";
import {ref, computed} from "vue";

export default {
    name: "Home",
    components: {
        Entry,
    },
    setup(props)
    {
        const items = ref([])
        const page = ref(1)
        const loaded = ref(false)
        const lastPage = ref(1)

        const fetchItems = async () =>
        {
            if (!loaded) {
                return null
            }

            // Apiリクエスト
            return await axios.get("item", {
                params: {
                    page: page.value,
                },
            });

            // 返り値は以下の通りとする
            {
                items: [],
                loaded: [],
                last_page: [],
            }
        }

        const pushItems = (content) =>
        {
            const responseItems = content.items

            // 配列に追加
            if (responseItems.length && !loaded.value) {
                responseItems.forEach((responseItem) =>
                {
                    items.value.push(responseItem);
                });
                // ページアップ
                page.value++;
                // set loaded
                loaded.value = content.loaded
                // set last page
                lastPage.value = content.last_page
            }
        }

        return {
            items,
            page,
            loaded,
            lastPage,
            fetchItems,
            pushItems,
        }
    }
};
</script>
vue3 composition apiでIntersectionObserverを使って無限スクロール(infinity loading)を作る! 2022-02-01 12:54:48

コメントはありません。

4069

お気軽に
お問い合わせください。

お問い合わせ
gomibako@aska-ltd.jp