Intersection Observer 高性能的观察元素的相交

559 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

前言

现代浏览器支持多种观察者模式

之前在很多文章以及插件上都有看到这关于观察者API,但是自己一直没有使用的场景,最近因为需求原因,用到了其中的 IntersectionObserverMutationObserver,也做了一些简单的研究,发现其功能之强大以及API之简单超出预期。

IntersectionObserver 相交(交叉)观察者

API参考

IntersectionObserver可以观察指定两个元素的相交情况,这简单的技术使用场景是:观察元素是否出现或退出屏幕,以此延伸出的业务需求如:

  • 图片懒加载
  • 无限滚动(触底加载)
  • 内容曝光统计
  • 开启或停止动画、视频等耗性能操作

其API非常简单,简化之后就两步:

  1. 创建观察者对象
  2. 调用方法开始观察
const observer = new IntersectionObserver(handler, options)

observer.observe(document.getElementById('#dom'))

其中创建实例化对象接收两个参数:

  • handler:两个元素相交关系发生变化时触发(!!注意是相交关系变化,而不是相交的时候触发一次)
  • options:观察者的配置,可以不传
    • root:观察者的根元素,如果不传或传null,则默认使用顶级文档的视窗(可理解为可见屏幕)
    • rootMargin:计算相交时,根元素的偏移量,可以有效的缩小或扩大根元素的判断范围。接受值类似了css border的值,可以是px或者%单位,可以单独指定元素四边的偏移量("0px 10px 20px 15px"),也可以指定四边使用同一个偏移量("10px")。整数表示扩大,负数表示缩小
    • thresholds:观察相交的阈值,可以是0-1之间的数字或该数字的数组,数字表示的是相交百分比,每当两个元素相交经过阈值范围后就会触发一次回调。默认值为0,表示只要有一个像素相交即触发回调,设置值为1时,元素需要完全出现在root后才会触发。

rootMargin的理解

rootMargin可能有些难以理解,我们可以通过画图来方便理解:

图片左侧是rootMargin为0的时候,两个元素并没有相交,而设置了rootMargin为正数值后,扩大了root的相交判定区域,所以原本不相交的两个元素触发了相交判定。

image

那如果设置为负数值也可想而知了,负数值会将root元素相交判定区域缩小,原本相交的两者可能会不触发相交!!

hander的形参

hander回调会在相交关系发生变化时,或者第一次监听目标元素的时候触发,所以触发了回调并不表示元素之间的相交了,我们还需要通过回调参数进行一些合理判断。

function hander(entries, observer) {
    entries.forEach(entry => {
        console.log(
            entry.rootBounds, // root元素的边界信息,会受到rootMargin值得影响,与Element.getBoundingClientRect()返回值相同。
            entry.boundingClientRect, // 目标元素的边界信息,与Element.getBoundingClientRect()返回值相同。
            entry.intersectionRatio, // 元素相交的比例值,0-1之间的小数
            entry.intersectionRect, // 元素相交部分的边界信息,与Element.getBoundingClientRect()返回值相同。
            entry.isIntersecting, //元素之间是否相交,Boolean
            entry.target, // 触发相交的目标元素
            entry.time // 触发相交的时间
        )
        
        console.log(
            observer.root,
            observer.rootMargin,
            observer.thresholds,
        )
    })
}
  • entries 数组,数组中每个元素的属性可以参考:IntersectionObserverEntry。(为什么是个数组?因为observer.observe可以调用多次,观察根元素和多个元素的相交)
  • observer 观察者的实例化对象,可以通过这个参数来获取options的值

其中entries中最常用的是 isIntersecting: 是否相交,如果你只观察一个元素,可以通过解构直接获取该数据:

new IntersectionObserver(([{ isIntersecting }]) => {
    if (isIntersecting) alert('相交了')
})

IntersectionObserver的方法

构建了IntersectionObserver的实例,并不会进行观察,还需要调用相关的方法才能正常使用:

  • observe(dom) 开始观察指定元素,入参需要一个目标元素,观察这个元素和root元素的相交关系,调用此方法后,hander才会被触发。
  • disconnect() 停止对所有目标元素的观察,诸如图片懒加载的功能,图片加载完成后实际上不需要在进行观察,可以通过这个方法停止观察。
  • unobserve(dom) 停止指定目标元素的观察,如果dom为空或者并未目标元素,并不会抛出异常,可安全使用
  • takeRecords() 返回目标元素的 entrie 信息(参考hander的参数)

值得注意的事项

  • hander会在主线程中被执行,所以函数中也应该避免耗时操作
  • 相交的计算会将所有的元素视为一个矩形,如果指定元素是不规则的,则视为包含元素所有区域的最小矩形