IntersectionObserver API实现PC端无限下滑、结合搜索功能

77 阅读3分钟

一、产品需求

在PC端实现移动端下滑“加载更多“的功能,需要结合条件搜索及滚动事件;

二、介绍及实现

交叉口观察员API、元素进入当前视图即可触发对应操作获取数据;

兼容性

image.png

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…