跳到主要内容

IntersectionObserver API 用法

· 阅读需 13 分钟
Oxygen

当我们说到图片懒加载、页面数据的滚动加载这些体验设计时,一般能够想到基于scroll事件,通过getBoundingClientRect方法获取元素相对于视口偏移量top,来判断元素是否可见,demo 如下

Edit jovial-snowflake-e42869

这种实现方式较为繁琐,但是现在我们有了IntersectionObserver API,可以大大简化这些通过计算元素偏移量来判断可视性的逻辑。

W3C 在 2017-09-14 正式发布了IntersectionObserver API 草案,其功能上提供了检测目标元素在祖先元素或 viewport 内可视情况变化的方式,这种观测是异步的,保证了开发上的便捷和性能上的高效。

IntersectionObserver类型定义

IntersectionObserver本身是一个构造函数,需要通过new来初始化调用,基本使用方式如下:

// 获取目标元素 DOM 对象
const observeTarget = document.querySelector("#targetId");

// 初始化IntersectionObserver实例
let observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
// e.isIntersecting 为 true,则目标元素变得可见
if (e.isIntersecting) {

}
})
}, {
threshold: [0, 1]
});

// 开始监测目标元素
observer.observe(observeTarget);

// 取消监测
observer.unobserve(observeTarget);
observer.disconnect();

下面是详细的 TypeScript 类型定义:

/*
* 构造函数
*/
declare var IntersectionObserver: {
prototype: IntersectionObserver;
new(callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;
};

/*
* 构造函数初始参数1:回调函数
*/
interface IntersectionObserverCallback {
(entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;
}

/*
* 构造函数初始参数2:回调函数执行条件设定
*/
interface IntersectionObserverInit {
/*
* 目标元素的包含元素,如果不指定则默认为 viewport
*/
root?: Element | Document | null;
/*
* 目标元素相对于 viewport 或者包含元素的偏移量
* 注意:负值表示目标元素在包含元素内部偏右或者偏下的位置
*/
rootMargin?: string;
/*
* 目标元素触发回调函数的
*/
threshold?: number | number[];
}

/*
* IntersectionObserver对象属性和方法
*/
interface IntersectionObserver {
readonly root: Element | Document | null;
readonly rootMargin: string;
readonly thresholds: ReadonlyArray<number>;
/*
* 停止监测所有元素
*/
disconnect(): void;
/*
* 开始监测目标元素
*/
observe(target: Element): void;
/*
* 获取所有目标元素的监测对象
*/
takeRecords(): IntersectionObserverEntry[];
/*
* 停止监测目标元素
*/
unobserve(target: Element): void;
}

/*
* 回调函数获取的目标元素的监测变量
*/
interface IntersectionObserverEntry {
/*
* 目标元素在 viewport 内部的坐标
*/
readonly boundingClientRect: DOMRectReadOnly;
/*
* 目标元素可见比例
*/
readonly intersectionRatio: number;
/*
* 目标元素可见区域在 viewport 内部的坐标
*/
readonly intersectionRect: DOMRectReadOnly;
/*
* 目标元素是否可见
*/
readonly isIntersecting: boolean;
/*
* 目标元素的root元素可见区域的坐标
*/
readonly rootBounds: DOMRectReadOnly | null;
/*
* 目标元素 DOM 对象
*/
readonly target: Element;
/*
* 相对于创建文档的时间,当元素可见性发生改变的时间对象
*/
readonly time: DOMHighResTimeStamp;
}

在初始化IntersectionObserver对象的时候,需要重点理解rootthreshold的概念。

root

root可以在初始化IntersectionObserver时指定,表示目标元素相对的容器元素,如果未指定则为html根元素。

root和目标元素都被看成是矩形盒子,而rootMargin则是目标元素相对于root的位置偏移量,因为root内部还可能排布的有其他元素或者root本身具有不为0margin属性。

  • rootMargin取正值时,元素在viewport或者祖先元素内部向上或者向左偏移

  • rootMargin取负值时,元素在viewport或者祖先元素内部向下或者向右偏移

(下图引自Building A Dynamic Header With Intersection Observer — Smashing Magazine

1-dynamic-header-intersection-observer

threshold

threshold用于指定一个或者多个在0 ~ 1范围内的数字,数字代表的是一个比例,表示元素在root内部可见面积相对于root的百分比。

当目标元素可见面积比例发生改变且在指定的阈值时,就会触发IntersectionObserver初始化时的回调函数,如果不指定,则默认为0,即元素在完全不可见和可见时触发回调函数。

警告

threshold并不能保证回调函数一定在这些指定的阈值到达时执行。

IntersectionObserver用例

判断元素是否可见

通过IntersectionObserver我们可以判断元素是否可见的两种情况:

  1. documentviewport内部是否可见,此时元素相对于浏览器窗口或者iframe定位
  1. 在祖先元素内部是否可见

Image Lazyload

Sticky Header BoxShadow

Infinite Scroll