IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
引用MDN对IntersectionObserver的介绍,对这接口就有大概的了解。Intersection Observer。 当发生交叉状态的时候,会触发调用相应的callback方法。
如上图,页面进行滚动的时候,右边的目标元素就与视窗发生交叉,交叉了说明在视图中是可见的。
构造函数
var observer = new IntersectionObserver(callback, options)
通过构造函数来生成一个实例observer,callback就是当目标元素可见性发生变化时触发的回调函数,options是一些配置项。
options
root
视窗元素:默认使用顶级文档的视窗,也可以指定某个元素。被观察的元素必须是视窗元素的子元素。
threshold
阈值配置:观察元素交叉区域与自身边界区域的比率,当观察元素越过指定的值后,就会触发回调函数。默认值为 0,类型可以是一个数值,也可以是一个数组。
var observer = new IntersectionObserver(callback, {
threshold: [0, 0.5, 1.0] // 数组类型
threshold: 1.0 // 数值类型
})
数组类型表示多个阈值,能升序排序。当观察元素区域与视窗发生0%,50%,100%交叉时,触发回调函数。 数值类型表示单个阈值,当观察元素区域与视窗发生100%交叉时,触发回调函数。
rootMargin
用于放大或缩小视窗元素的判断范围。
var observer = new IntersectionObserver(callback, {
rootMargin: "50px 50px 50px 50px"
})
4个数组分别表示 top, right, bottom, left 方向,默认值是:"0px 0px 0px 0px";
如上图,在原本viewport的区域上每个方向扩大了50px的判断距离,使得当前目标元素位置也能触发到回调函数。
callback
观察元素与视窗发生交叉就会触发回调函数,那传入回调函数参数有什么信息呢?
var callback = (entries, observer) => {
console.log(entries, observer)
}
var observer = new IntersectionObserver(callback)
entries 参数是个IntersectionObserverEntry对象数组。observer 参数是指当前IntersectionObserver实例。
IntersectionObserverEntry
IntersectionObserverEntry是描述了目标元素与其根元素容器在某一特定过渡时刻的交叉状态。 MDN上有详细介绍了。个人重点使用的就是 isIntersecting 和 target 属性。
isIntersecting 目标元素当前是否可见,Boolean类型;
target 观察的目标元素;
API
disconnect()
使当前IntersectionObserver对象实例停止监听工作。
observe()
使当前IntersectionObserver对象实例开始监听一个目标元素。
takeRecords()
返回所有观察目标的IntersectionObserverEntry对象数组。
unobserve()
使IntersectionObserver停止监听特定目标元素。
用途
用 IntersectionObserver 来实现懒加载,简单的多了,我们只需要设置回调,判断当前元素是否可见,再进行对应的操作(如:渲染图片、请求api)就好了,而不用去关心内部的计算。
function callback(entries, observer){
entries.forEach((entry) => { // 遍历entries数组
if(entry.isIntersecting){ // 当前元素可见
entry.target.src = entry.target.dataset.src
observer.unobserve(entry.target) // 停止观察当前元素,避免再次调用callback
}
})
}
const observer = new IntersectionObserver(callback)
let imgs = document.querySelectorAll('[data-src]')
imgs.forEach((item)=>{ // 循环把DOM元素添加到实例中去监听
observer.observe(item)
})
封装
class Observer {
constructor(options) {
this._init(options)
}
_init(options) {
var { root, rootMargin, thresholds, callback, loop } = options;
var observer_options = {
root,
rootMargin: rootMargin || '0px',
thresholds: thresholds || 0
}
var observer_callback = (entries, observer) => {
entries.forEach(entry => {
if (loop) {
callback(entry.target, entry.isIntersecting);
} else if ( entry.isIntersecting ) {
callback(entry.target);
observer.unobserve(entry.target);
}
})
}
this.$observer = new IntersectionObserver(observer_callback, observer_options)
}
observe(elements) {
var { $observer } = this;
var target;
$observer.disconnect();
for(let i = 0, len = elements.length; i < len; i++) {
target = elements[i];
target.$index = i; // 在目标元素上绑定一些我们需要的值
$observer.observe(target);
}
}
addObserve(element) {
this.$observer.observe(element)
}
reset(options) {
this.$observer.disconnect();
this._init(options);
}
}
二次封装,在原有基础上增加了一个 loop 选项,表示相同的目标元素是否会多次调用回调函数。并且在绑定监听目标元素上,为目标元素增加一些属性。还增设了重置的api,使得复用同一个实例。
function callback (target) {
console.log(target)
}
const ob = new Observer({
callback,
loop: true,
thresholds: 1
})
ob.observe(elements);