浏览器的5种Observer,无限滚动、懒加载

网页开发中处理用户交互会用 addEventListener 添加事件监听器来监听用户操作,比如 click、mousedown、mousemove等,这些都是由用户直接触发的事件。

那么对于一些不是由用户直接触发的事件呢? 比如元素从不可见到可见、元素大小的改变、元素的属性和子节点的修改等,这类事件如何监听呢?

浏览器提供了 5 种 Observer 来监听这些变动:MutationObserver、IntersectionObserver、PerformanceObserver、ResizeObserver、ReportingObserver。

一、IntersectionObserver

IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。

1、使用

var io = new IntersectionObserver(callback, option);

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点

// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();

2、callback

目标元素的可见性变化时,就会调用观察器的回调函数callback

callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

var io = new IntersectionObserver(
      entries => {
        console.log(entries);
      }
    );

callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

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
    }
复制代码

每个属性的含义如下。

  time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒

  target:被观察的目标元素,是一个 DOM 节点对象

  rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null

  boundingClientRect:目标元素的矩形区域的信息

  intersectionRect:目标元素与视口(或根元素)的交叉区域的信息

  intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为1,完全不可见时小于等于0

3、Option 对象

IntersectionObserver构造函数的第二个参数是一个配置对象。它可以设置以下属性。

1、threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

2、root 属性,rootMargin 属性

  很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在iframe窗口里滚动)。容器内滚动也会影响目标元素的可见性,IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。

4、应用场景

1、懒加载

  有时,我们希望某些静态资源(比如图片),只有用户向下滚动,它们进入视口时才加载,这样可以节省带宽,提高网页性能。这就叫做"惰性加载"。

  有了 IntersectionObserver API,实现起来就很容易了。

复制代码
function query(selector) {
      return Array.from(document.querySelectorAll(selector));
    }

    var observer = new IntersectionObserver(
      function(changes) {
        changes.forEach(function(change) {
          var container = change.target;
          var content = container.querySelector('template').content;
          container.appendChild(content);
          observer.unobserve(container);
        });
      }
    );

    query('.lazy-loaded').forEach(function (item) {
      observer.observe(item);
    });
复制代码

上面代码中,只有目标区域可见时,才会将模板内容插入真实 DOM,从而引发静态资源的加载。

2、无限滚动(infinite scroll)的实现也很简单。

复制代码
var intersectionObserver = new IntersectionObserver(
      function (entries) {
        // 如果不可见,就返回
        if (entries[0].intersectionRatio <= 0) return;
        loadItems(10);
        console.log('Loaded new items');
      });

    // 开始观察
    intersectionObserver.observe(
      document.querySelector('.scrollerFooter')
    );
复制代码

 3、曝光统计

参考:前端埋点之曝光实现

4、注意点

IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发。

IntersectionObserver的实现,应该采用requestIdleCallback(),即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。

 

二、MutationObserver

监听一个普通 JS 对象的变化,我们会用 Object.defineProperty 或者 Proxy,而监听元素的属性和子节点的变化,我们可以用 MutationObserver

MutationObserver 可以监听对元素的属性的修改、对它的子节点的增删改。

1、使用

const mutationObserver = new MutationObserver((mutationsList) => {
    console.log(mutationsList)
});

mutationObserver.observe(box, {
    attributes: true,
    childList: true
});

2、应用场景

比如文章水印被人通过 devtools 去掉了,那么就可以通过 MutationObserver 监听这个变化,然后重新加上,让水印去不掉。

三、ResizeObserver

窗口我们可以用 addEventListener 监听 resize 事件,那元素呢?

元素可以用 ResizeObserver 监听大小的改变,当 width、height 被修改时会触发回调。

const resizeObserver = new ResizeObserver(entries => {
    console.log('当前大小', entries)
});
resizeObserver.observe(box);

四、PerformanceObserver

PerformanceObserver 用于监听记录 performance 数据的行为,一旦记录了就会触发回调,这样我们就可以在回调里把这些数据上报。

const performanceObserver = new PerformanceObserver(list => {
      list.getEntries().forEach(entry => {
        console.log(entry);// 上报
      })
    });
    performanceObserver.observe({entryTypes: ['resource', 'mark', 'measure']});

创建 PerformanceObserver 对象,监听 mark(时间点)、measure(时间段)、resource(资源加载耗时) 这三种记录时间的行为。

比如 performance 可以用 mark 方法记录某个时间点:

performance.mark('registered-observer');

用 measure 方法记录某个时间段:

performance.measure('button clicked', 'from', 'to');

有了这些数据,就可以上报上去做性能分析了。

五、eportingObserver

ReportingObserver 可以监听过时的 api、浏览器干预等报告等的打印,在回调里上报,这些是错误监听无法监听到但对了解网页运行情况很有用的数据。

当浏览器运行到过时(deprecation)的 api 的时候,会在控制台打印一个过时的报告:

浏览器还会在一些情况下对网页行为做一些干预(intervention),比如会把占用 cpu 太多的广告的 iframe 删掉;会在网络比较慢的时候把图片替换为占位图片,点击才会加载;

这些干预都是浏览器做的,会在控制台打印一个报告:

这些干预或者过时的 api 并不是报错,所以不能用错误监听的方式来拿到,但这些情况对网页 app 来说可能也是很重要的:

比如我这个网页就是为了展示广告的,但浏览器一干预给我把广告删掉了,我却不知道。如果我知道的话或许可以优化下 iframe。

比如我这个网页的图片很重要,结果浏览器一干预给我换成占位图了,我却不知道。如果我知道的话可能会优化下图片大小。

所以自然也要监听,所以浏览器提供了 ReportingObserver 的 api 用来监听这些报告的打印,我们可以拿到这些报告然后上传。

const reportingObserver = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        console.log(report.body);//上报
    }
}, {types: ['intervention', 'deprecation']});

reportingObserver.observe();

 

参考:https://juejin.cn/post/7064557881492209678

https://www.cnblogs.com/goloving/p/8613261.html

posted @   菲比月  阅读(1146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示