ブログ
これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。
vue3 composition apiでIntersectionObserverを使って無限スクロール(infinity loading)を作る!
Okuda
2 years
vue3 composition apiで「無限スクロール」を作る!
Intersection Observer API
を使用して「無限スクロール」を実装。
Intersection Observer API
を使うことでパフォーマンスの問題を解消。
オブザーバの準備
IntersectionObserver
オブジェクトを作成し、交差を監視したい要素をobserveするには以下のように行う。
IntersectionObserver
の引数には callback
と option
を渡す。
パラメータには監視するエレメント 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)を作る!
vue3 composition apiでIntersectionObserverを使って無限スクロール(infinity loading)を作る!
2022-02-01 12:52:45
2022-02-01 12:54:48
コメントはありません。