浏览器的5种observer,你用过几种?

前端开发博客 前端开发博客     2022-12-01     784

关键词:

99%的前端开发者都关注了这个公众号 👇

点击上方 前端开发博客,关注并“设为星标”

回复加群,自助秒进前端群

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

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

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

我们分别来看一下:

IntersectionObserver

一个元素从不可见到可见,从可见到不可见,这种变化如何监听呢?

用 IntersectionObserver。

IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。

我们准备两个元素:

<div id="box1">BOX111</div>
<div id="box2">BOX222</div>

加上样式:

#box1,#box2 
    width: 100px;
    height: 100px;
    background: blue;
    color: #fff;

    position: relative;

#box1 
    top: 500px;

#box2 
    top: 800px;

这两个元素分别在 500  和 800 px 的高度,我们监听它们的可见性的改变。

const intersectionObserver = new IntersectionObserver(
    function (entries) 
        console.log('info:');
        entries.forEach(item => 
            console.log(item.target, item.intersectionRatio)
        )
    , 
    threshold: [0.5, 1]
);

intersectionObserver.observe( document.querySelector('#box1'));
intersectionObserver.observe( document.querySelector('#box2'));

创建一个 IntersectionObserver 对象,监听 box1 和 box2 两个元素,当可见比例达到 0.5 和 1 的时候触发回调。

浏览器跑一下:

可以看到元素 box1 和 box2 在可视范围达到一半(0.5)和全部(1)的时候分别触发了回调。

这有啥用?

这太有用了。我们在做一些数据采集的时候,希望知道某个元素是否是可见的,什么时候可见的,就可以用这个 api 来监听,还有做图片的懒加载的时候,可以当可视比例达到某个比例再触发加载。

除了可以监听元素可见性,还可以监听元素的属性和子节点的改变:

MutationObserver

监听一个普通 JS 对象的变化,我们会用 Object.defineProperty 或者 Proxy:

而监听元素的属性和子节点的变化,我们可以用 MutationObserver:

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

我们准备这样一个盒子:

<div id="box"><button>光</button></div>

加上样式:

#box 
    width: 100px;
    height: 100px;
    background: blue;

    position: relative;

就是这样的:

我们定时对它做下修改:

setTimeout(() => 
    box.style.background = 'red';
,2000);

setTimeout(() => 
    const dom = document.createElement('button');
    dom.textContent = '东东东';
    box.appendChild(dom);
,3000);

setTimeout(() => 
   document.querySelectorAll('button')[0].remove();
,5000);

2s 的时候修改背景颜色为红色,3s 的时候添加一个 button 的子元素,5s 的时候删除第一个 button。

然后监听它的变化:

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

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

创建一个 MutationObserver 对象,监听这个盒子的属性和子节点的变化。

浏览器跑一下:

可以看到在三次变化的时候都监听到了并打印了一些信息:

第一次改变的是 attributes,属性是 style:

第二次改变的是 childList,添加了一个节点:

第三次也是改变的 childList,删除了一个节点:

都监听到了!

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

当然,还有很多别的用途,这里只是介绍功能。

除了监听元素的可见性、属性和子节点的变化,还可以监听大小变化:

ResizeObserver

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

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

我们准备这样一个元素:

<div id="box"></div>

添加样式:

#box 
    width: 100px;
    height: 100px;
    background: blue;

在 2s 的时候修改它的高度:

const box = document.querySelector('#box');

setTimeout(() => 
    box.style.width = '200px';
, 3000);

然后我们用 ResizeObserver 监听它的变化:

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

在浏览器跑一下:

大小变化被监听到了,看下打印的信息:

可以拿到元素和它的位置、尺寸。

这样我们就实现了对元素的 resize 的监听。

除了元素的大小、可见性、属性子节点等变化的监听外,还支持对 performance 录制行为的监听:

PerformanceObserver

浏览器提供了 performance 的 api 用于记录一些时间点、某个时间段、资源加载的耗时等。

我们希望记录了 performance 那就马上上报,可是怎么知道啥时候会记录 performance 数据呢?

用 PeformanceObserver。

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

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

performance.mark('registered-observer');

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

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

后两个参数是时间点,不传代表从开始到现在。

我们可以用 PerformanceObserver 监听它们:

<html>
<body>
  <button onclick="measureClick()">Measure</button>

  <img src="https://p9-passport.byteacctimg.com/img/user-avatar/4e9e751e2b32fb8afbbf559a296ccbf2~300x300.image" />

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

    performance.mark('registered-observer');

    function measureClick() 
      performance.measure('button clicked');
    
  </script>
</body>
</html>

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

然后我们用 mark 记录了某个时间点,点击 button 的时候用 measure 记录了某个时间段的数据,还加载了一个图片。

当这些记录行为发生的时候,希望能触发回调,在里面可以上报。

我们在浏览器跑一下试试:

可以看到 mark 的时间点记录、资源加载的耗时、点击按钮的 measure 时间段记录都监听到了。

分别打印了这三种记录行为的数据:

mark:

图片加载:

measure:

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

除了元素、performance 外,浏览器还有一个 reporting 的监听:

ReportingObserver

当浏览器运行到过时(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();

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

文中的代码上传到了 github:https://github.com/QuarkGluonPlasma/browser-api-exercize

总结

监听用户的交互行为,我们会用 addEventListener 来监听 click、mousedown、keydown、input 等事件,但对于元素的变化、performance 的记录、浏览器干预行为这些不是用户交互的事件就要用 XxxObserver 的 api 了。

浏览器提供了这 5 种 Observer:

  • IntersectionObserver:监听元素可见性变化,常用来做元素显示的数据采集、图片的懒加载

  • MutationObserver:监听元素属性和子节点变化,比如可以用来做去不掉的水印

  • ResizeObserver:监听元素大小变化

还有两个与元素无关的:

  • PerformanceObserver:监听 performance 记录的行为,来上报数据

  • ReportingObserver:监听过时的 api、浏览器的一些干预行为的报告,可以让我们更全面的了解网页 app 的运行情况

这些 api 相比 addEventListener 添加的交互事件来说用的比较少,但是在特定场景下都是很有用的。

浏览器的 5 种 Observer,你用过几种呢?在什么情况下用到过呢?不妨来讨论下。

推荐链接

  1. 从破解某设计网站谈前端水印(详细教程)

  2. 前端程序员简历模板整理和下载

  3. 奇怪!生成网页水印,为啥总扯到 MutationObserver?

关注公众号 前端开发博客,回复以下关键词

查看更多优质内容!

加群 | 进阶 | 电子书 | 资料 | 面试 

简历 | 简历模板 | 思维图 | 知识点 | Vue脑图

分享好文和朋友一起看~

创作不易,加个点赞、在看 支持一下哦!

springmvc方法三种类型返回值总结,你用过几种?(代码片段)

SpringMVC现在算是Java领域的一个基础性框架了,很多人天天用,可是对于SpringMVC方法的返回值,你又是否完全清楚呢?今天松哥就来和大家聊一聊SpringMVC中四种不同类型的返回值,看看有没有get到你的知识盲点&... 查看详情

springmvc方法三种类型返回值总结,你用过几种?(代码片段)

SpringMVC现在算是Java领域的一个基础性框架了,很多人天天用,可是对于SpringMVC方法的返回值,你又是否完全清楚呢?今天松哥就来和大家聊一聊SpringMVC中四种不同类型的返回值,看看有没有get到你的知识盲点&... 查看详情

mybatis的三种分页方式,你用过几种?

前言分页是我们在开发中绕不过去的一个坎!当你的数据量大了的时候,一次性将所有数据查出来不现实,所以我们一般都是分页查询的,减轻服务端的压力,提升了速度和效率!也减轻了前端渲染的压力... 查看详情

mybatis的三种分页方式,你用过几种?

前言分页是我们在开发中绕不过去的一个坎!当你的数据量大了的时候,一次性将所有数据查出来不现实,所以我们一般都是分页查询的,减轻服务端的压力,提升了速度和效率!也减轻了前端渲染的压力... 查看详情

编译工具:这些ide和代码编辑器你用过几个?

首先我们应该分辨IDE和代码编辑器之间的区别。简单的说,IDE是一组集成在一起的工具:文本编辑器、编译器、构建或进行集成、调试等。通常IDE仅限于一种编码语言或框架。但有时我们只需要用来编辑代码的工具——... 查看详情

这12款idea插件你用过几款?

1.KeypromoterX2.StringManipulation3.BackgroundImagePlus4.CodeGlance5.Requestmapper6.Translation7.AlibabaJavaCodingGuidelines8.RainbowBrackets9.IndentRainbow10.GrepConsole11.MaterialThemeUI12.leetcodee 查看详情

10款免费的电路设计软件,你用过几个?

点击上方“小麦大叔”,选择“置顶/星标公众号”福利干货,第一时间送达工程软件和在线资源往往比较昂贵,但是对于专业人员、学生和爱好者来说非常有益。用户开展项目或者仅进行工程验证时,这些资源往... 查看详情

10款免费的电路设计软件,你用过几个?

点击上方“小麦大叔”,选择“置顶/星标公众号”福利干货,第一时间送达工程软件和在线资源往往比较昂贵,但是对于专业人员、学生和爱好者来说非常有益。用户开展项目或者仅进行工程验证时,这些资源往... 查看详情

这10种神级性能优化手段,你用过几个?

推荐阅读:应用架构、技术架构、安全架构、部署架构上篇引言:取与舍软件设计开发某种意义上是“取”与“舍”的艺术。关于性能方面,就像建筑设计成抗震9度需要额外的成本一样,高性能软件系统也意味着... 查看详情

数据分析和可视化必备的几大软件,你用过几个?

本文主要是面向数据分析初学者,因此分享的基本是一些免编程的可视化工具,详细介绍了7款工具,推荐大家使用,主要是让初学数据分析的朋友知道可视化工具大概有哪些、流行的有哪些。PowerBI PowerBI是微软... 查看详情

你用过不写代码就能完成一个简单模块的组件么?(代码片段)

开篇四连问你是否懒得写普通的增删改查方法?你是否不喜欢代码生成插件的重复代码?你是否渴望一个没有冗余代码的项目?你是否渴望一行代码都不用写就能完成一个简单的模块?组件由来作为后端程序员,相信大家都写过... 查看详情

2019年10个最佳linux发行版,你用过几个?如何选择适合自己的?

参考技术A2019年即将结束。虽然Linux的世界确实提供了很多选择,但一开始它可能会让人不知所措。这就是为什么我们准备本指南来帮助您选择最适合您需要的Linux发行版的原因。有些发行版在一种任务上表现更好,有些则是多面... 查看详情

解决geoserver请求跨域的几种思路,第二种思路用过

1.背景描述    跨域问题是浏览器同源安全制引起的特别常见的问题。不同前端语言针对跨域解决方法有所区别。比如Flex语言做跨域请求时,如果中间件存有跨域文件(crossdomain.xml)则可以轻松实现跨域。  ... 查看详情

简述自己用过的几种异步调用方式

直接上代码1.BeginInvoke和EndInvoke方式privatestaticvoidBeginInvoke1(){Func<int,string>fun=Todo;for(inti=0;i<5;i++){//fun.BeginInvoke(i,TodoCallBack,fun);/*异步调用委托BeginInvoke*handler.EndInvoke(x)为执行委托的 查看详情

.net几种微服务框架,你用过吗?(代码片段)

最近有群友问,.NET有哪些微服务框架?.NET的微服务框架还真不多,一般企业都会自己搭建微服务框架,或者基于其它框架搭建微服务(比如abp)。本文将介绍几种微服务框架,供大家学习参考。一、ServiceFabric简介:ServiceFabric... 查看详情

这14种嵌入式实时系统,你用过哪些?(代码片段)

满足实时控制要求的嵌入式操作系统(RTOS)以下介绍14种主流的RTOS,分别为μClinux、μC/OS-II、eCos、FreeRTOS、mbedOS、RTX、Vxworks、QNX、NuttX,而国产的嵌入式操作系统包括都江堰操作系统(djyos)、AliosThings、HuaweiLiteOS、R... 查看详情

这14种嵌入式实时系统,你用过哪些?(代码片段)

满足实时控制要求的嵌入式操作系统(RTOS)以下介绍14种主流的RTOS,分别为μClinux、μC/OS-II、eCos、FreeRTOS、mbedOS、RTX、Vxworks、QNX、NuttX,而国产的嵌入式操作系统包括都江堰操作系统(djyos)、AliosThings、HuaweiLiteOS、R... 查看详情

5个非常有用的laravelblade指令,你用过哪些?(代码片段)

接下来我将带大家认识下五个LaravelBlade指令,这些指令将让你在解决特定问题时如虎添翼。如果你是刚接触Laravel的用户,这些小技巧能带你认识到LaravelBlade模板引擎的便捷与高效。废话少说,让我们开始吧。1.检测用户是否认证... 查看详情