浏览器的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
:目标元素的可见比例,即intersectionRect
占boundingClientRect
的比例,完全可见时为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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具