GitHub大佬提出监听 DOM 的方式 Observer,瞬间引起业内热潮
myzbx 2025-09-12 00:23 32 浏览
前言
随着浏览器不断革新,JS 原生层面提供了一些 Observer API 来应对一些 观察和监听 DOM 的交互场景。比如:
- 监听 DOM 元素 自身属性和子节点的变化,可以使用 MutationObserver API;
- 监听 DOM 元素的 尺寸信息 的变化(如:width/height),可以使用 ResizeObserver API;
- 监听 DOM 元素是否 处于屏幕可视区域(视窗内),可以使用 IntersectionObserver API。
可见这三种 Observer API 各有所长,根据各自的特性能够解决不同的应用场景。
下面,我们来介绍这三种 Observer API 的用法以及其各自的应用场景。
一、MutationObserver 监听 DOM 节点信息变化
当你期望对一个 DOM 元素子节点的增加、减少、或者自身属性的变动、文本内容的变动 能够接收到通知,MutationObserver API 可以帮你来完成:它提供了监视 DOM 树内容变化的能力。
1. 特点:
- 属于 异步动作,它不同于传统的 发布订阅 同步发送通知,而是等 DOM 中所有的变更都结束后触发一次,能够有效避免重复触发通知;
- 一次通知记录了所有变动信息,它把 DOM 变动记录封装成一个数组进行统一处理;
- 按需监听,可根据场景自由选择要观察 DOM 哪些信息的变化(监听配置)。
2. 用法:
MutationObserver 自身是一个构造函数,接收一个 callback 作为监听回调,在 DOM 信息发生变化时被执行;同时返回一个 observer 对象,后续通过它来监听 DOM。
const observer = new MutationObserver(mutations => {
console.log(mutations); // 变动记录
});observer.observe(target[, options]) 用于监听 DOM 元素,第一参数 是指定要观察的 DOM,第二参数 是一个配置对象,用于指定所要观察元素的变动信息。
observer.observe(document.querySelector('#container'), {
childList: true, // 监视元素 第一级直接子节点 的变动
subtree: true, // 监视元素 所有后代节点 的变动(前提要求 childList = true)
attributes: true, // 监视元素 属性 的变动
characterData: true, // 监视元素或子节点树中 文本节点内容 的变化
attributeOldValue: true, // 记录 发生改动前的值,用于同步 `MutationRecord.oldValue`
attributeFilter: [], // 需要观察的特定属性名(比如 `['class', 'src']`)
});在构造函数中,callback 的参数 mutations,是一个是描述所有变动记录的 MutationRecord 对象数组,通过遍历 mutations 处理每一条变动记录。
const observer = new MutationObserver(mutations => {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});单个 MutationRecord 实例包含了变动相关的信息,包含以下属性:
- type: string:变动的类型,值包含 attributes、characterData 或 childList(对应 childList 和 subtree 配置);
- target: Element:发生变动的 DOM 节点;
- addedNodes: NodeList:返回新增的 DOM 节点,如果没有节点被添加,则返回一个空的 NodeList;
- removedNodes: NodeList:返回移除的 DOM 节点,如果没有节点被移除,则返回一个空的 NodeList;
- previousSibling:返回被添加或移除的节点之前的兄弟节点,如果没有则返回 null;
- nextSibling:返回被添加或移除的节点之后的兄弟节点,如果没有则返回 null;
- attributeName:返回被修改的属性的属性名,如果设置了 attributeFilter,则只返回预先指定的属性;
- oldValue:变动前的值。这个属性只对 attribute 和 characterData 变动有效,如果发生 childList 变动,则返回 null。
如果要停止对 DOM 元素的变动监听,可执行:
observer.disconnect();3. 场景:
对于现在流行的数据驱动框架来说,元素的属性、文本内容、及子节点的渲染和移除,多数都是采用数据 state 来控制,所以在项目框架下使用 MutationObserver 的场景并不是很多。
但对于接入第三方 SDK 启动的程序,如 WPS 文档、会议 SDK。在没有提供相关 API 的情况下,要去监听它里边的元素信息变化,可以去借助 MutationObserver 来实现一些监听动作。
使用示例:
// 示例来自:mdn web docs MutationObserver
// https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
// 选择需要观察变动的节点
const targetNode = document.getElementById("some-id");
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback = function (mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.type === "childList") {
console.log("A child node has been added or removed.");
} else if (mutation.type === "attributes") {
console.log("The " + mutation.attributeName + " attribute was modified.");
}
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
observer.disconnect();二、ResizeObserver 监听 DOM 尺寸信息变化
ResizeObserver API 可用于监视一个 DOM 的尺寸信息变化。可以是 宽高、位置 信息的变化。
1. 用法:
使用 ResizeObserver 遵循四个步骤:创建观察者、定义监听回调、定义观察的目标对象、取消观察。
- 创建观察者 observer:
const resizeObserver = new ResizeObserver(callback);- 定义监听回调 callback:
const callback = (entries) => {
entries.forEach(entrie => {
console.log(entrie.target, entrie.contentRect);
})
}在回调中每个 entrie 都是一个对象,包含两个属性 contentRect 和 target。contentRect 类似于 ele.getBoundingClientRect() 记录了元素的 位置 和 尺寸 信息。
- 定义观察的目标对象
resizeObserver.observe(container);- 取消观察
取消单个节点的观察:
resizeObserver.unobserve(container);取消所有节点观察:
resizeObserver.disconnect();2. 场景:
2.1 针对元素的不同尺寸定义不同样式
在业务上要实现页面在不同尺寸的设备上布局排版自适应,可选用两种方式实现:
- CSS @media screen 媒体查询,根据视窗大小应用不同的布局样式;
- JS window.resize 监听视窗变化,在视窗发生变化时动态干预布局。
但这两种方式都是应用在 window 视窗上的,若现在遇到需求要监听 div 的尺寸变化(比如 宽、高)如何做呢?
现有一个需求:监听 div 容器宽度变化,调整其内部的布局结构。
比如一个容器宽度支持被拖动调整尺寸大小,并根据宽度大小对内部元素做不同自适应布局。
这类似于 css @media screen 媒体查询,但由于媒体查询只能用于监听视窗,对于监听 元素 尺寸可以使用 JS ResizeObserver API 来实现。
useEffect(() => {
const target = messagesAreaRef.current!; // 目标元素
const resizeObserver = new ResizeObserver(entries => {
const { width } = entries[0].contentRect; // 监听到的尺寸信息
if (width < 842) { // 目标值
target.classList.add("compact"); // 添加紧凑 class
} else {
target.classList.remove("compact");
}
});
resizeObserver.observe(target);
return () => {
resizeObserver.disconnect();
};
}, []);2.2 监听目标元素尺寸变化同步应用
需求:我们要实现一个文件预览器(弹框),点击文件可以打开弹框进行预览。
通常文件预览器是一个全屏的弹框,我们可以通过 fixed 固定定位 轻松实现。
但现在需求升级,能够让预览弹框支持在特定区域内展示,也就是说它可以不是一个全屏的弹框,而是与页面中指定的容器区域内展示。
一种是基于容器元素使用 absolute 绝对定位来实现,但这种方式实现要求将 预览弹框的内容 挂载到这个容器 DOM 树内。
如果我们希望 预览弹框 能够挂载到 document.body 下,且还能实现在特定区域内呈现预览内容,我们需要通过 fixed + 获取容器的位置及尺寸来实现。
// container 为外部传入的容器元素,没有传则以
const ele = container || document.body;
const { left, top, width, height } = ele.getBoundingClientRect();
ReactDOM.createPortal(
<div
className="file-preview-container"
ref={previewContainerRef}
style={{ position: 'fixed', zIndex: 3000, left, top, width, height }}>
...
</div>,
document.body,
)但现在有一个问题是:如果调整容器的尺寸,预览弹框不会跟着实施调整宽高,这时我们借助 ResizeObserver 来监听容器的尺寸变换,并同步给预览弹框。
const previewContainerRef = useRef<HTMLDivElement>(null);
const ele = container || document.body;
...
useEffect(() => {
const resizeObserver = new ResizeObserver(entries => {
const entrie = entries[0];
const { width, height } = entrie.contentRect;
// 同步尺寸
previewContainerRef.current?.style.setProperty('width', width + 'px');
previewContainerRef.current?.style.setProperty('height', height + 'px');
});
resizeObserver.observe(ele);
return () => {
resizeObserver.unobserve(ele);
}
}, []);三、IntersectionObserver
IntersectionObserver API 是一个交叉观察器,用于监听 目标元素 与指定的 root 元素(祖先元素或视窗) 的交叉状态(可见性)。
这对 检测某个(些)元素是否出现在可视区域 的需求非常适用。如常见的业务需求:图片懒加载、视频出现在可视区域时自动播放 等。
目标元素 与 root 元素 交叉 存在以下几种情况:
- 目标元素 即将进入 root 元素的可见区域内,处于 未交叉 状态;
- 目标元素 进入 root 元素的可见区域内,处于 交叉 状态(又分为 部分交叉 和 完全展示在其中);
- 目标元素 离开 root 元素的可见区域内,处于 未交叉 状态。
如果你对 交叉 一词不太容易理解,可以把它看成是:目标元素是否处于 root 容器(有滚动条)的可视区域内
1. 用法:
constructor
IntersectionObserver 是一个构造函数,接收 callback 和 options 作为参数,通过 new 关键字创建一个对象实例。
const io = new IntersectionObserver(callback, options);callback
callback 是一个监听目标元素在 root 根元素中的 交叉状态 的回调函数。在回调中可通过参数 entries 来获取目标元素于 root 的交叉信息。
IntersectionObserver` 默认初始化会触发一次 callback,并在第一时间拿到 目标元素与 root 容器的交叉状态。
entries 中包含了多个观察对象(支持监听多个目标元素),每个 entrie 代表一个目标元素的监听信息,具体如下:
- target - HTMElement: 交叉的目标元素;
- isIntersecting - Boolean: 是否处于交叉状态,当目标元素出现在 root 可视区域(交叉)时值为 true,离开了 root 可视区域时值为 false(取决下方于配置项 阈值 options.threshold);
- intersectionRatio - Number:返回目标元素出现在 root 可视区域的比例,范围是 0 - 1,为 1 表示完全可见;
- boundingClientRect - Object: 返回包含目标元素 与 root 元素的边界信息,返回结果与 element.getBoundingClientRect() 一致;
- intersectionRect - Object: 目标元素与视口(或根元素)的交叉区域的信息,同 getBoundingClientRect() 方法的返回值;
- time: 返回从监听目标元素开始,在触发交叉时的时间(时间戳,类似于 performance.now())。
判断交叉状态常用的两个信息是:isIntersecting 和 intersectionRatio。
options
options 用于配置 目标元素 与 root 元素出现 交叉 的规则,共三个属性:
- root - HTMLElement | null: 指定交叉参照的祖先元素,在未传入时使用定义文档的视窗;
- rootMargin - String: 用来扩展或缩小 rootBounds 这个矩形的大小,从而影响 intersectionRect 交叉区域的大小。所有的偏移量均可用像素(px)或百分比(%)来表达, 默认值为"0px 0px 0px 0px"。
- threshold - Array | Number: 决定了触发回调函数的交叉规则。它是一个数组,默认为 [0],仅在出现交叉时执行一次回调。当设置 1 时,表示目标元素需要 100% 可见时触发回调,当设置 [0, 0.25, 0.5, 0.75, 1] 就表示当目标元素 0%、25%、50%、75%、100% 可见时,分别触发回调函数。
methods
拿到 IntersectionObserver 的实例后,可使用下列方法来 监听/停止监听 目标元素。
- observe(HTMLElement): 监听一个目标元素;
- unobserve(HTMLElement): 停止监听目标元素;
- takeRecords(): 返回所有观察的目标对象的 entrie 信息;
- disconnect(): 停止所有目标元素的监听工作。
2. 场景:
2.1 图片懒加载提高
判断元素是否出现在可视区域,比较常见的场景是 图片懒加载。
假如进入页面要渲染 100 条带有图片的数据,如果将它们全部进行网络资源请求,这将会阻塞页面的其他 API 请求。尽管很多图片并未出现在页面的可视区域内。
图片懒加载可以很好的控制页面中图片资源加载的时机,从而提高网站的访问速度。
思路:在图片没有出现在视窗时展示默认占位图(或者统一的封面图,且资源很小),当触发滚动 图片 出现在视窗后,替换为真实的图片的地址。
<img src="./loading-url.png" data-src="./url.png" alt="">
const imgList = [...document.querySelectorAll('img')];
const io = new IntersectionObserver((entries) =>{
entries.forEach(item => {
// isIntersecting 是一个 Boolean 值,判断目标元素当前是否进入 root 视窗
if (item.isIntersecting) {
item.target.src = item.target.dataset.src; // 真实的图片地址存放在 data-src 自定义属性上
// 图片加载后停止监听该元素
io.unobserve(item.target);
}
});
})
// observe 监听所有 img 节点
imgList.forEach(img => io.observe(img));2.2 元素的页面曝光埋点
当一个页面中的特定元素,完全显示在可视区内时进行埋点曝光。
const boxList = [...document.querySelectorAll('.box')];
var io = new IntersectionObserver((entries) =>{
entries.forEach(item => {
if (item.intersectionRatio === 1) { // 元素完全出现在可视区域
// ... 增加埋点曝光逻辑
io.unobserve(item.target);
}
})
}, {
root: null,
threshold: 1, // 阀值设为 1,当只有比例达到 1 时才触发回调函数
})
// observe遍历监听所有box节点
boxList.forEach(box => io.observe(box))聊一聊监听 DOM 的方式 Observer
原文链接:
https://juejin.cn/post/7277830857422290956
相关推荐
- 如何设计一个优秀的电子商务产品详情页
-
加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...
- 怎么在JS中使用Ajax进行异步请求?
-
大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...
- 中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革
-
前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...
- 前端监控 SDK 开发分享_前端监控系统 开源
-
一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...
- Ajax 会被 fetch 取代吗?Axios 怎么办?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...
- 前端面试题《AJAX》_前端面试ajax考点汇总
-
1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...
- Ajax 详细介绍_ajax
-
1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...
- 6款可替代dreamweaver的工具_替代powerdesigner的工具
-
dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
-
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
- 福斯《死侍》发布新剧照 "小贱贱"韦德被改造前造型曝光
-
时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...
- 2021年超详细的java学习路线总结—纯干货分享
-
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...
- 不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!
-
Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...
- 2025 年 Python 爬虫四大前沿技术:从异步到 AI
-
作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...
- 最贱超级英雄《死侍》来了!_死侍超燃
-
死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...
- 停止javascript的ajax请求,取消axios请求,取消reactfetch请求
-
一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)
