一、产品需求
在PC端实现移动端下滑“加载更多“的功能,需要结合条件搜索及滚动事件;
二、介绍及实现
交叉口观察员API、元素进入当前视图即可触发对应操作获取数据;
兼容性
vue3实现代码
1.elementUI时间线+数据为空时、数据加载完毕后的交互;
<div ref="timelineContainer" class="timeline-container">
<el-timeline>
<el-timeline-item v-for="(item, index) in statusList" :key="index" color="#409EFF">
<span>{{ item.xxx }}</span>
<span>{{ item.xxx }}</span>
<span>
<pre>{{ item.xxx }}</pre>
</span>
</el-timeline-item>
</el-timeline>
// 观察元素
<div ref="observerElement"></div>
// 搜索数据为空
<div v-if="xxx" class="end-of-content">数据已全部加载完毕~</div>
// 数据加载完毕
<div v-else-if="xxx" class="empty-container">
<img :src="require('@/assets/empty.png')" alt="" />
<p>暂无数据</p>
</div>
</div>
其中<div ref="observerElement"></div>为需要观察的元素,放在时间线里面,用户滑动到此处,触发接口请求获取数据;
2.API部分
Step1:vue3需要定义如下引用元素;
const observerElement = ref(null);
Step2:在onMounted里面监听
onMounted(() => {
observer.value = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
getOperateRecordList(); //发接口请求拿数据
}
},
{
root: null,
threshold: 0
}
);
observer.value.observe(observerElement.value);
});
其中,root属性用于指定目标元素所在的容器节点,必须是被观察元素的祖先元素,然后将该根元素用作相交的视口;threshold表示intersection元素进入视图多少后可以触发事件,取值为0~1之间;其余api在相关资料链接均有详细介绍;
Step3:组件销毁前停止监听
onBeforeUnmount(() => {
observer.value.disconnect();
});
3.其余代码
(1)时间线数据更新
const getOperateRecordList = async () => {
// 步骤一:自定义状态-如果所有数据已加载,停止执行函数,避免多次触发事件
if (allDataLoaded.value) {
return;
}
//步骤二:自定义参数处理
let params = handleParams(searchForm.value);
params = {
...params,
deviceId: props.deviceId,
pageNumber: pages.value.pageNumber,
pageSize: pages.value.pageSize
};
// 步骤三:数据获取
const { content = [], metadata = {} } = await api.xxxx(params);
// 步骤四:数据重置与追加
if (pages.value.pageNumber === 1) {
statusList.value = content; // 如果是第一页,重置数据
} else {
statusList.value.push(...content); // 如果不是第一页,追加数据
}
pages.value.totalCount = metadata.page.totalItems;
pages.value.pageNumber += 1; // 准备加载下一页
// 步骤五:数据长度大于总数-触底展示控制
if (statusList.value.length >= pages.value.totalCount) {
allDataLoaded.value = true;
}
};
(2)搜索条件发生改变后,滚动到当前元素最顶部
const timelineContainer = ref(null); // 容器
const onSearchFormChange = (data = {}) => {
// 输入搜索条件后,页码更新成第一页
pages.value.pageNumber = 1;
// 数据是否加载完毕手动置为否-否则上述(1)中的数据allDataLoaded为true的情况下,不会再触发接口
allDataLoaded.value = false;
getOperateRecordList();
// 滚动到当前容器最顶部
nextTick(() => {
if (timelineContainer.value) {
timelineContainer.value.scrollTop = 0;
}
});
}
三、踩坑及解决
1.observerElement前不要写数据加载完毕或者无数据的用户提示,即
<div ref="observerElement"></div>
<div v-if="xxx" class="end-of-content">数据已全部加载完毕~</div>
<div v-else-if="xxx" class="empty-container">
<img :src="require('@/assets/empty.png')" alt="" />
<p>暂无数据</p>
</div>
不要写成
<div v-if="xxx" class="end-of-content">数据已全部加载完毕~</div>
<div v-else-if="xxx" class="empty-container">
<img :src="require('@/assets/empty.png')" alt="" />
<p>暂无数据</p>
</div>
<div ref="observerElement"></div>
因为此处xxx是依赖于接口返回数据而更新状态,这时候会存在一种边界情况,比如如下情况中:
“加载完毕”元素已经显示,ovserverElement紧随其后但是未暴露在视图中。当用户搜索条件发生变更后,当前页面xxx条件发生变更,“加载完毕”元素被移除,导致ovserverElement元素暴露在视图中,会触发数据更新,再结合当前的搜索条件触发的数据更新事件,导致两次接口请求被触发;
四、IntersectionObserver API相关资料
www.ruanyifeng.com/blog/2016/1… blog.webdevsimplified.com/2022-01/int… juejin.cn/post/711206…