window.IntersectionObserver 类似于window.addEventListener,当指定dom可见/不可见的时候触发,我们用它快速开发一些下拉刷新/下滑加载,图片懒加载,长列表性能优化的功能。这个是MDN的示例。
这是IntersectionObserver兼容性,不用兼容IE的同学可以大胆使用。
我们先用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>