在使用vant组件库写移动端网页时,vant组件库并没有提供相关上拉加载的组件,因此只能自己手写一个,在了解了两种方案
(1)计算滚动条滚动的距离
(2)使用IntersetionObserver API
的简易程度后,决定使用IntersetionObserver API来实现上拉加载,这个方案比第一种简单太多了。
要完成上拉加载,我们首先需要对页面的布局做相应的处理,具体布局如下:
<div class="team-container">
<van-search
v-model="searchValue.searchText"
show-action
placeholder="搜索队伍"
shape="round"
@search="handleSearchTeam"
>
<template #action>
<div @click="addTeam">新增</div>
</template>
</van-search>
<div class="team-list">
<team-card-list
ref="cardListRef"
:team-list="teamList"
:fetch-data="fetchData"
@join-team="doJoinTeam"
@update-team="doUpdateTeam"
/>
</div>
</div>
页面tabbar部分为固定定位,脱离了文档流,搜索框为粘性定位,不会因为页面的滚动而滚动,因此只需要计算出剩下内容的高度,并设置overflow:scroll,并将父容器设置为overflow:hidden,这样就初步具备了上拉加载的可能。
.team-container {
overflow: hidden;
height: 100%;
.team-list {
overflow-y: scroll;
height: 100%;
}
}
做好上面的步骤以后,我们还需要一个媒介,当这个媒介显示时,我们才开始触发上拉加载功能,这样就用到了IntersetionObserver API,当这个媒介在浏览器中显示时,这个API会监听到他的出现。这里,我选择了vant组件库提供的loading组件,需要注意的是,这个loading组件我们需要将它放在我们展示的所有内容的最后,不然IntersetionObserver API可能监听不到它的出现,类似下面:
<template v-for="user in userList" :key="user.id">
<van-card
:desc="user.profile"
:title="`${user.username} (${user.planetCode})`"
>
<template #thumb>
<van-image :src="user.avatarUrl" round width="80" height="80" />
</template>
<template #tags>
<template v-for="(tag, index) in user.tags" :key="index">
<van-tag type="primary">{{ tag }}</van-tag>
</template>
</template>
</van-card>
</template>
<div class="loading">
<van-loading
type="spinner"
size="24px"
v-loading="fetchData"
v-if="isShow"
/>
</div>
做好以上布局之后,开始使用自定义指定来监听loading组件的出现,之所以使用自定义指令,是因为编写自定义指令在全局注册之后无论哪个页面都能使用。
使用mounted这个钩子函数使得使用自定义指令的元素在挂载完成后调用,我们在使用自定义指令时可以传递一个可执行函数,我们在mounted这个钩子函数中开启监听loading组件的显示与隐藏,当显示时执行我们传递的可执行函数来加载新的数据,这样子就完成上拉加载了。
import type { Directive } from "vue";
import { useSettingStore } from "@/store/modules/setting";
type IPageInfo = {
pageNumber: number;
pageSize: number;
};
const settingStore = useSettingStore();
const vUpLoading: Directive<HTMLElement, (pageInfo?: IPageInfo) => void> = {
mounted(el, binding) {
let pageNumber = 1;
const pageSize = 10;
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.intersectionRatio > 0) {
settingStore.setLoading(true);
binding.value({
pageNumber: ++pageNumber,
pageSize,
});
}
});
observer.observe(el);
},
};
编写好这个指令之后,在主运行文件注册一下。
import vUpLoading from "./directives/up-loading";
const app = createApp(App);
app.directive("loading", vUpLoading);
注册完成之后,直接在页面元素使用就行了。
<van-loading
type="spinner"
size="24px"
v-loading="fetchData"
v-if="isShow"
/>