IntersectionObserver懒加载

270 阅读3分钟

IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。

引用MDN对IntersectionObserver的介绍,对这接口就有大概的了解。Intersection Observer。 当发生交叉状态的时候,会触发调用相应的callback方法。

image.png
如上图,页面进行滚动的时候,右边的目标元素就与视窗发生交叉,交叉了说明在视图中是可见的。

构造函数

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"; image.png

如上图,在原本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);