专栏首页云前端[译] 精通 Intersection Observer API

[译] 精通 Intersection Observer API

原文:https://www.hweaver.com/intersection-observer-single-page-navigation/

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

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

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

这些方法都运行在主线程中,这意味着一个地方出现问题就会殃及所有事情。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 将任意物体都视为矩形以便计算。这些矩形在包含目标内容的前提下,将被尽可能小的计算。

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

最后至关重要的是,要理解不同于传统 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 很好的填补了空白。官方的代码和文档可以在这里找到:https://github.com/w3c/IntersectionObserver/tree/master/polyfill

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

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

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

总结

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

本文分享自微信公众号 - 云前端(fewelife),作者:云前端

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [译] JS在浏览器和Node下是如何工作的?

    在 JavaScript 王国的土地上,无数开发者在前端或后端领域热情耕耘着。JS 易于理解,也是前端开发中不可或缺的部分。但不同于其他编程语言,这玩意是单线程...

    江米小枣
  • RESTful简介

    REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。 Fielding,他是HTTP协议(1.0版和1.1版)的主要设计者...

    江米小枣
  • immutable.js 简介

    Immutable数据就是一旦创建,就不能更改的数据。每当对Immutable对象进行修改的时候,就会返回一个新的Immutable对象,以此来保证数据的不可变

    江米小枣
  • 前端小知识:为什么你写的 height:100% 不起作用?

    作者:JiaXinYi https://segmentfault.com/a/1190000012707337 这个知识不算冷门的,但是用的时候可能还是会有些懵...

    企鹅号小编
  • JAVA集合类基础知识

    把具有相同性质的一类东西汇聚成一个整体,就可以称为集合。一般数据存储结构分为以下几种:

    heasy3
  • 入门 | egg.js 入门之egg-jwt

    这里创建并安装完成以后,需要再次初始化俩包,分别为egg-cors与egg-jwt token 生成的验证包

    mySoul
  • 微服务的10个挑战和解决方案

    我是一名云API开发人员和架构师,目前正致力于为美国的大型零售客户提供基于Google GCP的微服务。

    歪脖贰点零
  • WPF 解决 StylusPlugIn 点击穿透问题

    在使用 StylusPlugIn 的时候会出现这样的坑,只要一个元素附加有 StylusPlugIn 在这个元素上面放另一个没有附加 StylusPlugIn ...

    林德熙
  • 微服务的10个挑战和解决方案

    我是一名云API开发人员和架构师,目前正致力于为美国的大型零售客户提供基于Google GCP的微服务。

    Java架构师历程
  • 页面性能优化的五种办法

    大部分用户希望网页能在 2 秒之内就完成加载。事实上,加载时间每多 1 秒,你就会流失 7% 的用户。如果加载需要太长时间,他们就会放弃访问。

    小生方勤

扫码关注云+社区

领取腾讯云代金券