利用交叉观察器api实现页面滚动加载
· 阅读需 5 分钟
一直以来滚动加载用的是监听滚动事件,然后计算底部的距离,由于是在主线程上执行往往会遇到性能问题,虽然可以通过防抖节流之类的方式解决,总归觉得有些丑陋,今天试到了一个新的 交叉观察器 API
可以更优雅地实现这些功能的检测。
从名字就很容易发现这是一个用来检测dom元素是否形成交叉的 API,在目标元素和目标视口发生一定程度的重叠变化时来触发通知。
首先调用 new IntersectionObserver
构造方法获得 IntersectionObserver
实例,需要传入两个参数,第一个是必选参数交叉事件的回调函数,回调函数接受两个参数:
/**
* 回调函数
* @param entries - 一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
* @param observer - 被调用的IntersectionObserver实例。
*/
const callback = (entries, observer) => {
entries.forEach((entry) => {
console.log(entry)
// 每个条目描述一个目标元素观测点的交叉变化:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
}
在滚动加载场景中,只需简单的判断 entry.isIntersecting
就可以了,它是一个布尔值,true
表示形成交叉。
第二个为可选参数,是由以下属性组成的配置对象:
const options = {
root: document.querySelector("#scrollArea"), // 监听的视口,必须是目标的祖先。如果未指定或为 null,则默认为浏览器视口
rootMargin: "0px", // 根周围的边距,跟 css 的 margin 同语法,会对交叉的判断造成偏移,默认为 0
threshold: 0.1, // 表示在视口中,目标可见度达到多少百分比时触发回调函数,取值 0 ~ 1之间,默认为 0,意味着一旦形成交叉就触发回调事件,也可以是一个数组,那就会在经过每个阈值时触发回调
}
值得一提的是回调函数不只是形成交叉才会触发,从交叉阈值回到不交叉的状态也会触发,他强调的是 经过指定的阈值时产生回调。
在创建完观察器之后,需要为观察器配置观察对象,只需调用实例的 observer
函数即可:
observer.observe(document.getElementById('loading'));
在了解完这些之后,下面举个🌰作为演示案例:
<style>
.box {
height: 100px;
margin: 10px;
border: 1px solid #000;
text-align: center;
}
.loading {
height: 100px;
line-height: 100px;
text-align: center;
background: linear-gradient(to bottom, #ff0 10%, #f0f 10%);
}
</style>
<body>
<div id="list"></div>
<div class="loading" id="loading">load more</div>
<script>
let page = 1;
const pageSize = 10;
const batchCreateBox = (count) => {
for (let i = 0; i < count; i++) {
const box = document.createElement('div');
box.className = 'box';
box.innerText = `box${pageSize * (page - 1) + i + 1}`;
document.getElementById('list').appendChild(box);
}
}
let isLoading = false;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry);
if (entry.isIntersecting && !isLoading) {
document.getElementById('loading').innerText = 'loading...';
isLoading = true;
// 模拟请求延迟
setTimeout(() => {
batchCreateBox(pageSize);
document.getElementById('loading').innerText = 'load more';
page++;
isLoading = false;
}, 1000)
}
});
}, {
threshold: 0.1,
});
observer.observe(document.getElementById('loading'));
</script>
</body>
这里我配置了在 loading
元素出现 0.1(即10%)
的时候触发回调事件,另外定义了一个 isLoading
变量防止在获取数据过程中多次触发。
需要注意的是,如果第一次获取的数据长度不足以把 loading
元素挤到设置的阈值外,是不会出发后续的加载的,这点在使用时需要考虑好 pageSize
的大小。