IntersectionObserver API

1,037 阅读4分钟

IntersectionObserver

随着网页发展,对检测某些元素是否出现在可视窗相关的需求越来越多了,然而前端要检测一个元素是否可见的成本比较大。IntersectionObserver 应运而生

offsetTop < clientHeight + scrollTop
element.getBoundingClientRect().top < clientHeight

Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。
Intersection Observer API 允许你配置一个回调函数,当以下情况发生时会被调用

  • 每当目标(target)元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。
  • Observer第一次监听目标元素的时候

创建一个 IntersectionObserver对象,并传入相应参数和回调用函数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。 一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

let options = {
    root: document.querySelector('#scrollArea'), 
    rootMargin: '0px', 
    threshold: 1.0
}
let callback = (entries, io) => {}

let observer = new IntersectionObserver(callback, options);

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

方法

  • observe(element)开始监听一个目标元素
  • disconnect()方法终止对所有目标元素可见性变化的观察。
  • unobserve()方法命令IntersectionObserver停止对一个特定元素的观察。
  • takeRecords() 返回所有观察目标的IntersectionObserverEntry对象数组,如果使用回调来监视这些更改,则无需调用此方法。调用此方法会清除挂起的相交状态列表,因此不会运行回调。
const item = document.querySelector('.item')
io.observe(item)
io.unobserve(item)
io.disconnect()

参数

options

  • root
    指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。
  • rootMargin
    根(root)元素的外边距。类似于 CSS 中的 margin 属性,比如 "10px 20px 30px 40px" (top, right, bottom, left)。如果有指定root参数,则rootMargin也可以使用百分比来取值。该属性值是用作root元素和target发生交集时候的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。默认值为0。
  • threshold
    可以是单一的number也可以是number数组,target元素和root元素相交程度达到该值的时候IntersectionObserver注册的回调函数将会被执行。如果你只是想要探测当target元素的在root元素中的可见性超过50%的时候,你可以指定该属性值为0.5。如果你想要target元素在root元素的可见程度每多25%就执行一次回调,那么你可以指定一个数组[0, 0.25, 0.5, 0.75, 1]。默认值是0(意味着只要有一个target像素出现在root元素中,回调函数将会被执行)。该值为1.0含义是当target完全出现在root元素中时候 回调才会被执行。
 let opts = {
        root: document.querySelector('.container'),
        rootMargin: "-50px 0px 0px 0px",
        threshold: [0, 0.5, 0.75, 1]
    }
const io = new IntersectionObserver(callback, opts)

callback

当元素可见比例超过指定阈值后,会调用一个回调函数

    let callback = (entries, io) => {}
    const io = new IntersectionObserver(callback, opts)
  • entries 是一个数组,每个成员都是一个IntersectionObserverEntry对象, 数组的长度是我们监控目标元素的个数。
  • io 就是被调用的IntersectionObserver 实例。

IntersectionObserverEntry

IntersectionObserverEntry对象提供目标元素的信息,一共有六个属性。

{
  time: 3893.92,
  rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  boundingClientRect: ClientRect {
     // ...
  },
  intersectionRect: ClientRect {
    // ...
  },
  intersectionRatio: 0.54,
  target: element
}
属性说明
target被观察的目标元素,是一个 DOM 节点对象
isIntersecting布尔值, 如果目标元素与root元素相交,则返回 true,否则false
intersectionRect目标元素与root元素交叉区域的信息
intersectionRatio返回目标元素出现在root元素的比例
boundingClientRect目标元素的边界信息
rootBounds根元素的矩形区域的信息
time可见性发生变化的时间,是一个高精度时间戳,单位为毫秒

应用场景

懒加载

图片懒加载——当图片滚动到可见时才进行加载

let callback = (entries) => {
    entries.forEach(item => {
        if (item.isIntersecting) {
          item.target.src = item.target.dataset.src
          // 图片加载后即停止监听该元素
          io.unobserve(item.target)
        }
    })
}
const imgList = [...document.querySelectorAll('img')]
const observer = new IntersectionObserver(callback);

imgList.forEach(function (item) {
  observer.observe(item);
});

无限滚动

let callback = (entries) => {
    if (entries[0].isIntersecting){
        let tag = entries[0].target.dataset.tag
        if(tag === 'top') {
           loadNew(); 
        } else {
           loadOld(); 
        }
        
    }
}
const observer = new IntersectionObserver(callback);
let topTag = document.querySelector('.top_tag')
let bottomTag = document.querySelector('.bottom_tag')
observer.observe(topTag);
observer.observe(bottomTag);

scrollama

一个用 IntersectionObserver 进行滚动显示的JavaScript库 example

npm install scrollama intersection-observer --save
import 'intersection-observer'
import scrollama from "scrollama"

const scroller = scrollama();
scroller
  .setup({
    step: ".step",
  })
  .onStepEnter((response) => {
    // { element, index, direction }
  })
  .onStepExit((response) => {
    // { element, index, direction }
  });

window.addEventListener("resize", scroller.resize);

属性

属性说明
step触发更改元素
offset距视口顶部多远才能触发步骤
progress是否触发增量步骤进度更新 false
threshold进度间隔的粒度(以像素为单位)
order如果前一步触发器被跳过,则触发它们。 true
once仅触发输入一次的步骤,然后删除侦听器。false
debug是否显示可视调试工具。false

方法

方法参数
onStepEnter(callback)当step元素的顶部或底部边缘进入偏移阈值时触发的回调。element、index、direction
onStepExit(callback)step元素的顶部或底部边缘超出偏移阈值时触发的回调。 element、index、direction
onStepProgress(callback)触发进度(0-1)达到阈值的回调。 element、index、progress
offsetTrigger()获取或设置偏移百分比
resize()获取浏览器/ DOM的最新尺寸
disable()停止观察触发器的变化
enable()恢复观察触发变化
destroy()删除所有观察者和回调函数

应用场景

浏览器可视区内特定位置的数据

this.scroller = scrollama()
this.scroller
  .setup({
  step: '.table-row',
  offset: 0.5 // 取屏幕可视区域中间的数据
})
  .onStepEnter(response => {
  let { element, index, direction } = response
  let item = this.tableData[index]
  this.debonceInitTime(item)
})
debonceInitTime: _.throttle(function(item) {
  if (!this.$itool.isEmpty(item?._time ?? null)) {
     // do something
      }
}, 500)
}

获取元素显示的进度

let scroller = scrollama();
let handleStepProgress = (response) => {
    console.log(response.progress);
    let progress = (response.progress*100).toFixed(0)
    response.element.getElementsByTagName('span')[0].innerHTML = `${progress}%`
}
scroller
    .setup({
        step: ".list .item",
        progress: true,
        offset: 0.5
    })
    .onStepProgress(handleStepProgress);

参考资源

Intersection Observer
IntersectionObserver API 使用教程
scrollama