使用 IntersectionObserver, 10行代码手写一个上滑加载

165 阅读1分钟

window.IntersectionObserver 类似于window.addEventListener,当指定dom可见/不可见的时候触发,我们用它快速开发一些下拉刷新/下滑加载,图片懒加载,长列表性能优化的功能。这个是MDN的示例

这是IntersectionObserver兼容性,不用兼容IE的同学可以大胆使用。 image.png

我们先用IntersectionObserver手写上滑无限刷新

核心代码

利用短短 IntersectionObserver 几行代码就能实现上滑加载

// 和addEventListener不同,需要先写触发后执行的动作
const showEnd = new IntersectionObserver(function (entries) {
  if (entries[0].intersectionRatio > 0) { // 可见,加载下一行
    ajax();
  }
});
showEnd.observe(endDom); // 需要监听的元素

同理,我们可以再实现顶部下拉刷新

const showTop = new IntersectionObserver(function (entries) {
  if (entries[0].intersectionRatio <= 0) { // 不可见
    page = 1 // 当前页面跑到第一页
    // 此处按自己需求处理,比如B站就是把新数据加载unshift前部
    // data.length = 0 // 比如,删除已有的数据,类似于掘金等大多数场景
    ajax();
  }
});
showEnd.observe(topDom);

因为observe允许传入多个dom,可以把两个逻辑合并到一个监听事件里边。以vue3为例,完整代码如下

demo的全部代码

// 在掘金上直接写的代码,没测试,有报错的地方同学可以自己改改

<script setup lang="ts">
import { reactive, onBeforeUnmount, onMounted, computed } from "vue";

// vue3的this.$refs,类似于子组件/dom的onMounted生命周期
// 可以用在v-for循环中,与循环的item绑定,后期就会好找很多
// 此处demo只是演示用法,平时直接使用 ref() 就可以
const ulDom = reactive({
  dom: null, // 容器dom
  endDom: null, // 触发下一页的dom
  topDom:null // 此处可以稍微拓展一下,下拉刷新,因想避免太长代码,所以暂时舍弃
});
const data = reactive({ // 模仿真实场景
  list: [],
  page: { // 分页
    size: 10,
    current: 1,
    total: 10000,
  },
});
let loding = false;
function ajax(clear) { // 模仿jajx
  if (loding) return;
  loding = true;
  setTimeout(() => {
    loding = false;
    if(clear) {
      data.list = []
      nexttick(() => { // 隐藏顶部按钮,下次接着用
        ulDom.dom.scrollTop = 40 
      })
    }
    const { size, current } = data.page;
    const last = data.list[data.list.length - 1] || 0;
    for (let i = last; i <= size + last; i++) {
      data.list.push(i);
    }
  }, 100);
}
// 和addEventListener不同,需要先写触发后执行的动作
const showEnd = new IntersectionObserver(function (entries) {
  if (entries[0].intersectionRatio > 0) { // 底部可见,加载下一行
    ajax();
    return
  }
  if (entries[1].intersectionRatio > 0) { // 顶部可见,加载所有
    data.current = 1
    ajax(true);
    return
  }
});
onMounted(() => {
  ajax()
  showEnd.observe(ulDom.endDom); // 此处获取new两个不同的observe可能也会比较好
  showEnd.observe(ulDom.topDom); // 但我还是放到一个observe了,哈哈哈
});
</script>
<template>
    <ul :ref="el => ulDom.dom = el">
      <li :ref="el => ulDom.topDom = el"></li>
      <li v-for="li in data.list" :key="li">第{{ li }}行</li>
      <li :ref="el => ulDom.endDom = el"></li>
    </ul>
</template>
<style>
ul {
  width: 200px;
  border: 2px solid red;
  margin: 100px auto 0;
  overflow: auto;
  height: 500px;
}
ul li {
  line-height: 40px;
}
</style>