[译] 精通 Intersection Observer API

3,105 阅读6分钟

原文:www.hweaver.com/intersectio…

现代网站重度依赖 scroll 事件并不是什么秘密了。滚动可以触发图片懒加载或延迟请求数据、初始化动画、支持无尽内容的加载,如此等等。糟糕的是这些 scroll 事件都不太可靠,也都是资源消耗大户。这在实现效果方面引起了问题,也常常让浏览器不堪重负。

作为一种处理 scroll 事件的新方式,交集观察者(Intersection Observer API) 应运而生。该 API 允许用户观察指定元素 A,并监视其与特定元素 B (或浏览器视口)的 交集(intersection) 状态。

既有的实现究竟有何问题?考虑一个当下典型的站点页面,有很多 scroll 事件在发生 -- 广告模块、从底部滚动进来的新内容、时不时需要运行动画的元素,或是页面中的很多图片,都会滚动至被用户看到后才会加载或执行。这些 scroll 事件关联了无数的循环调用方法,其中不乏比如需要获得必要位置信息的 Element.getBoundingClientRect() 等等,都是性能敏感的。

这些方法都运行在主线程中,这意味着一个地方出现问题就会殃及所有事情。Intersection Observer API 让浏览器免于应付交集事件,通过使用关联特定元素的交集状态的回调函数取而代之。浏览器可以更有效地管理这些事件,性能也得到了优化。

需要注意的是浏览器兼容性,截至本文被翻译时的统计如下:

Intersection Observer API 的兼容性

概念 & 基本用法

为了完全理解为何 Intersection Observer API 更益于性能,先来看看基础知识。

IntersectionObserver 定义

定义一个 Intersection Observer 实例时,有一些关键的术语。

根(root) 指的是等待一个对象与其产生交集的某个元素。默认来说,就是浏览器视口(viewport),但任何合法的元素都是可以使用的。

除了以 root 作为一个单独 IntersectionObserver 的基础,观察者还可以监视许多不同的 目标(target)。目标也可能是任意合法的元素,当任何一个目标和根元素发生交集时,观察者会触发一个回调函数。

根元素碰撞

基本用法

建立一个简单的 IntersectionObserver 非常方便。首先调用 IntersectionObserver构造器,并向其传入一个回调函数和一个预设的选项:

const options = {
	root: document.querySelector('#viewport'),
	rootMargin: '0px',
	threshold: 1.0
};

const observer = new IntersectionObserver(callback, options);

如上所示,选项中有一些可用的属性:

root

用来检查是否和目标元素发生交集的根元素。该选项接受任何合法元素,但是根元素必须是目标元素的祖先,这一点很重要。如果不指定根元素,或设为 null,则浏览器视口就作为默认的根元素。

rootMargin

该属性被用来扩展或缩减根元素的尺寸。接受和 CSS 中的 margin 相同格式的值,比如一个单独的值 10px 或定义不同边的多个值 10px 11% -10px 25px

threshold

最后,threshold(译注:阈yù值)选项指定了一个最小量,表示目标元素和根元素交集时,其自身满足该最小量才会触发回调。取值为 0.0 – 1.0 之间的一个浮点数,所以 75% 左右的交集率应该写成 0.75。如果希望在多个点触发回调,也可以传入一个值的数组,如 [0.33, 0.66, 1.0]

一旦 IntersectionObserver 实例被创建,剩下所要做的就是提供一个或多个目标元素以供观察:

const target = document.querySelector('#target');
observer.observe(target);

从此,回调函数将会在目标(或多目标)接近交集阈值的时刻被触发。

const callback = function(entries, observer) {
	entries.forEach((entry) => {
		// 在这干点什么
	});
}

交集的计算

理解交集如何计算是重要的。首先,Intersection Observer API 将任意物体都视为矩形以便计算。这些矩形在包含目标内容的前提下,将被尽可能小的计算。

Bounding box outlines
目标矩形的边界轮廓

对于根元素,基于 rootMargin 的值考虑其矩形边界,这个值会填充或减小根元素的尺寸。

Root Margin Calculations
计算 Root Margin

最后至关重要的是,要理解不同于传统 scroll 事件的是,Intersection Observer 并不是在每次交集改变后不间断地轮询。相反,回调只在阈值大约达到时被调用。如果需要多次检测,提供多个阈值就行了。

Demo 1 – 动画

在第一个小项目中,我们用一种简单的方式来看看 Intersection Observer API。

// 动画回调函数
const scrollImations = (entries, observer) => {
	entries.forEach((entry) => {
		console.log(111, entry.isIntersecting, entry.intersectionRatio);
		
		if(entry.isIntersecting && entry.intersectionRatio >= 1) {
		    // 完全看到元素时
			entry.target.classList.add('box--visible');
		} else {
			entry.target.classList.remove('box--visible');
		}
	});
}

// 创建观察者
const options = {
	threshold: 1.0,
};
const observer = new IntersectionObserver(scrollImations, options);

// 指定观察目标
const boxes = document.querySelectorAll('.box');
boxes.forEach((box) => {
	observer.observe(box);
});

进入视口后触发的动画

向下滚动,一系列元素会出现。用一个 IntersectionObserver 实例监视 3 个目标元素。当它们完全进入视口(root)后,向目标元素上附加一个样式类名,触发对应的 CSS 动画。

Demo 2 – 页内导航

对于单页中随着滚动、相应某个区域的出现而高亮的导航条,Intersection Observer 是很适用的。

页内导航

// 初始化观察者
const options = {
	threshold: 0.45
}

const observer = new IntersectionObserver(changeNav, options);

// 指定目标元素
const sections = document.querySelectorAll('section');
sections.forEach((section) => {
	observer.observe(section);
});

changeNav() 回调函数简单的检查目标 section 元素是否足够多的出现在屏幕上,然后恰当地指定样式类名。

const changeNav = (entries, observer) => {
	entries.forEach((entry) => {
		// 检查元素发生了碰撞
		if(entry.isIntersecting && entry.intersectionRatio >= 0.55) {
			// 删除旧的 active 样式类
			document.querySelector('.active').classList.remove('active');
			// 取得满足条件的目标元素 id
			var id = entry.target.getAttribute('id');
			// 找到匹配的元素并添加类名
			var newLink = document.querySelector(`[href="#${id}"]`).classList.add('active');
		}
	});
}

浏览器支持 & Polyfill

对于尚不支持该特性的浏览器,有 polyfill 很好的填补了空白。官方的代码和文档可以在这里找到: github.com/w3c/Interse…

更简单的方法是适用 polyfill.io (polyfill.io)。可以单独指定需要加载的 Polyfill,且满足条件的浏览器才会加载。这可以保证页面的轻量,同时又不用过多配置。其用法如下:

<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>

一旦 polyfill 被加载,以上 demos 就能在 Safari、IE7+ 等浏览器上运行了。

总结

如你所见,Intersection Observer API 简单易用又富创造性。尽管可能需要 polyfill,但浏览器支持也在持续改善。该 API 将成为前端优化的利器。



--End--

搜索 fewelife 关注公众号

转载请注明出处