MutationObserver
基本使用
- MutationObserver 提供了监听 DOM 树变化的能力,可以追踪 DOM 树的变化,包括节点的增加、删除、属性的修改等;
- 它是异步的,这意味着它会将所有的 DOM 变化集合起来一次性报告,而不是在每一次变化后立刻报告;
// 1. 选择要监听的DOM节点 const targetNode = document.getElementById('target'); if (!targetNode) return; // 避免节点不存在时报错 // 2. 配置监听选项:指定要监听哪些DOM变化(必选,至少配置一项) const config = { attributes: true, // 监听属性变化 childList: true, // 监听子节点的增删(含文本节点) subtree: true, // 监听目标节点的**所有后代节点**(默认仅目标节点本身) attributeOldValue: true, // 变化时返回**旧的属性值**(默认仅返回新值) characterData: true, // 监听文本节点的内容变化 characterDataOldValue: true, // 文本变化时返回旧值 attributeFilter: ['class', 'style'] // 仅监听指定属性(白名单,可选) }; // 3. 创建MutationObserver实例:回调函数处理DOM变化 // mutations:变化记录数组(批量存储所有符合条件的DOM变化) // observer:当前MutationObserver实例(可在回调中手动停止观察) const observer = new MutationObserver((mutations, observer) => { mutations.forEach(mutation => { // 根据变化类型做不同处理 switch (mutation.type) { case 'attributes': console.log('属性变化:', mutation.attributeName); console.log('旧值:', mutation.oldValue); console.log('当前值:', mutation.target.getAttribute(mutation.attributeName)); break; case 'childList': console.log('子节点增删:'); console.log('新增节点:', mutation.addedNodes); // 新增节点集合(NodeList) console.log('删除节点:', mutation.removedNodes); // 删除节点集合(NodeList) break; case 'characterData': console.log('文本内容变化:'); console.log('旧文本:', mutation.oldValue); console.log('新文本:', mutation.target.data); break; } }); }); // 4. 启动监听:将观察者与目标节点、配置绑定 observer.observe(targetNode, config); // 5. 停止监听(按需调用,如组件销毁、节点移除时) // observer.disconnect(); // 6. 清空未处理的变化队列(可选,disconnect后队列仍存在,需手动清空) // observer.takeRecords(); // 返回未处理的变化数组,同时清空队列
应用场景
-
场景 1:监听动态渲染的 DOM 节点 (如异步加载的元素)
// 监听目标节点是否出现 function waitForElement(selector, callback) { const target = document.body; // 监听整个body const observer = new MutationObserver((mutations) => { // 检查是否出现目标节点 const element = document.querySelector(selector); if (element) { observer.disconnect(); // 找到节点后停止监听 callback(element); // 执行回调,传入找到的节点 } }); // 仅监听子节点增删,性能最优 observer.observe(target, { childList: true, subtree: true }); } // 使用:等待id为"async-box"的节点出现后,修改其内容 waitForElement('#async-box', (el) => { el.textContent = '异步节点已找到!'; }); -
场景 2:监听元素样式 / 属性变化 (如 class 变化)
const btn = document.getElementById('submit'); const observer = new MutationObserver((mutations) => { mutations.forEach(m => { if (m.attributeName === 'disabled') { const isDisabled = btn.hasAttribute('disabled'); console.log('按钮状态变化:', isDisabled ? '禁用' : '启用'); // 同步更新其他元素状态 document.getElementById('tip').style.color = isDisabled ? '#999' : '#333'; } }); }); // 仅监听disabled属性变化 observer.observe(btn, { attributes: true, attributeFilter: ['disabled'] }); -
场景 3:监听富文本 / 输入框的文本变化 (兼容非常规输入)
const editor = document.getElementById('rich-editor'); const observer = new MutationObserver((mutations) => { console.log('富文本内容变化:', editor.innerHTML); // 实时保存草稿、字数统计等 }); // 监听后代的文本和子节点变化(富文本核心变化) observer.observe(editor, { childList: true, subtree: true, characterData: true }); -
场景 4:Vue 中监听组件内 DOM 变化 (避免内存泄漏)
<template> <div ref="container">目标容器</div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; const container = ref(null); let observer = null; onMounted(() => { if (!container.value) return; // 创建观察者 observer = new MutationObserver((mutations) => { console.log('组件内DOM变化:', mutations); }); // 启动监听 observer.observe(container.value, { childList: true, subtree: true }); }); // 组件销毁时停止监听,释放资源 onUnmounted(() => { if (observer) { observer.disconnect(); observer = null; // 解除引用,方便GC回收 } }); </script>
案例:DOM 变化监控面板
IntersectionObserver
基本使用
-
IntersectionObserver 是一个现代的浏览器 API,用于监听目标元素与根元素 / 视口的交叉状态变化 (简单说:目标元素是否进入 / 离开可视区域,或与根元素有重叠);
-
它非常适合实现图片懒加载、无限滚动、广告曝光率等功能;
// 1. 定义根元素(可选,默认视口,必须是目标元素的祖先) const rootElement = document.querySelector('.root-container'); // 2. 创建 IntersectionObserver 实例【完整语法】 const observer = new IntersectionObserver( /** * 交叉状态变化的回调函数(异步执行) * @param {IntersectionObserverEntry[]} entries - 所有被监听目标的交叉状态对象数组 * @param {IntersectionObserver} self - 当前观察器实例(可在回调内操作) */ (entries, self) => { // 遍历所有触发状态变化的目标 entries.forEach(entry => { // 核心:判断目标是否与根元素交叉(进入/停留) if (entry.isIntersecting) { console.log('目标进入根区域', entry.target); // 可选:交叉比例(0~1) console.log('交叉比例:', entry.intersectionRatio); // 可选:目标元素的布局信息 console.log('目标位置:', entry.boundingClientRect); // 可选:交叉区域的布局信息 console.log('交叉区域:', entry.intersectionRect); // 可选:根元素的布局信息(根为视口时是视口尺寸) console.log('根区域:', entry.rootBounds); // 可选:状态变化的时间戳(页面加载到触发的毫秒数) console.log('触发时间:', entry.time); // 示例:进入后停止监听该目标(避免重复触发,如懒加载) self.unobserve(entry.target); } else { console.log('目标离开根区域', entry.target); } }); }, /** * 配置项(全参数,均为可选,注释为默认值) * @type {IntersectionObserverInit} */ { root: rootElement, // 参考根元素:null=视口,DOM元素=指定祖先容器 rootMargin: '0px 0px 0px 0px', // 根边距:格式同CSS margin(上 右 下 左),支持px/百分比,正负均可 threshold: 0, // 触发阈值:0~1单个值 / 数组,如[0,0.5,1]表示0%/50%/100%交叉时均触发 trackVisibility: false, // (高级)是否跟踪元素可见性(如被opacity/visibility隐藏),默认false delay: 0 // (高级)回调触发延迟(毫秒),避免频繁触发,默认0 } ); // 3. 获取需要监听的目标元素(支持单个/多个) const target1 = document.querySelector('.target-1'); const target2 = document.querySelector('.target-2'); const allTargets = document.querySelectorAll('.target-item'); // 4. 监听目标元素【核心方法】 // 监听单个目标 observer.observe(target1); observer.observe(target2); // 监听多个目标(推荐:一个实例监听多目标,性能更优) allTargets.forEach(target => observer.observe(target)); // 5. 停止监听【核心方法,防内存泄漏】 // 停止监听单个目标 observer.unobserve(target1); // 停止监听所有目标并销毁观察器(彻底释放资源,SPA路由切换必用) observer.disconnect();
应用场景
-
图片懒加载:当图片元素即将进入视口时,触发加载该图片资源,从而优化页面加载速度和减少不必要的带宽消耗;
// 1. 创建观察器 const lazyImgObserver = new IntersectionObserver( (entries) => { entries.forEach(entry => { // 目标进入视口 if (entry.isIntersecting) { const img = entry.target; // 将data-src赋值给src img.src = img.dataset.src; // 停止监听该图片(避免重复触发) lazyImgObserver.unobserve(img); // 图片加载失败的兜底 img.onerror = () => { img.src = "默认错误图.png"; }; } }); }, // 根边距:提前50px加载,提升用户体验 { rootMargin: '0px 0px 50px 0px' } ); // 2. 监听所有懒加载图片 document.querySelectorAll('.lazy-img').forEach(img => { lazyImgObserver.observe(img); }); -
无线滚动加载:在用户滚动页面接近底部时,可以用来实现更多内容的动态加载;
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { loadMoreContent(); // 加载更多内容的函数 observer.unobserve(entry.target); // 如果只想加载一次,可以在这里停止观察 } }); }); const loadMoreTrigger = document.querySelector('.load-more-trigger'); observer.observe(loadMoreTrigger); -
广告跟踪与统计:监测广告是否被用户实际看到,以便进行广告曝光量的计算和计费;
-
视频或音频自动播放:当媒体元素进入视口时,开始播放视频或音频,避免页面载入时就播放多个媒体导致资源浪费;
-
交互式视差滚动效果:根据元素在视口中的位置变化,动态调整其 CSS 样式,创建深度感强烈的滚动动画效果;
-
性能优化:对不在视口内的组件或区块进行渲染优化,比如使用骨架屏代替未加载的内容,待其进入视口时再渲染完整内容;
案例:元素曝光动效
ResizeObserver
基本使用
-
ResizeObserver 是浏览器原生提供的异步观察 API,用于监听 DOM 元素的尺寸变化 (包括宽高、边框、内边距等布局尺寸),也能监听元素的容器尺寸变化;
-
ResizeObserver 提供了一种高效的方法来响应这些变化,而不需要频繁使用事件监听器或轮询技术;
// 1. 创建 ResizeObserver 实例,传入尺寸变化的回调函数 const resizeObserver = new ResizeObserver((entries) => { // entries 是尺寸变化的元素数组(可同时监听多个元素) entries.forEach((entry) => { console.log('被监听的元素:', entry.target); // 元素的布局尺寸(包含padding,不含border,对应content-box) console.log('宽度:', entry.contentRect.width); console.log('高度:', entry.contentRect.height); console.log('内边距:', entry.contentRect.top, entry.contentRect.left); }); }); // 2. 获取目标元素,开始监听(核心方法:observe) const target = document.getElementById('target-box'); if (target) { // 基础监听:默认监听 content-box resizeObserver.observe(target); // 进阶监听:指定监听的盒模型(padding-box/border-box) // resizeObserver.observe(target, { box: 'border-box' }); } // 3. 不再需要时,销毁监听(避免内存泄漏,核心方法:disconnect/unobserve) // 取消单个元素的监听 // resizeObserver.unobserve(target); // 取消所有元素的监听并销毁实例 // resizeObserver.disconnect();
应用场景
-
响应式布局:当网页布局需要根据容器大小变化而自适应时,可以使用 ResizeObserver 来监听容器元素的大小变化,并相应地调整布局和样式;
-
数据可视化:在使用如 ChartJs 等图表库创建图表时,可以结合 ResizeObserver 来实现图表的自适应大小;
-
富文本编辑器:在富文本编辑器中,内容的变化可能导致滚动条和布局的调整;
-
拖放功能:在实现拖放功能时,拖动元素可能会改变目标区域的尺寸,可以实时更新目标区域的尺寸,以适应拖动的元素,并触发相应的操作;
-
图片和媒体元素的自适应:当网页中包含图片、视频或其他媒体元素时,可以使用 ResizeObserver 来监听其容器元素的大小变化,自动调整媒体元素的大小和布局,确保媒体元素在不同设备和屏幕尺寸下都能正确显示;
案例:尺寸监听
PerformanceObserver
基本使用
-
在前端性能分析领域,了解和监控网页的运行性能至关重要,PerformanceObserver API 供了一个强大的工具来监听性能时间线上的事件,从而实现更细粒度的性能监控;
-
PerformanceObserver 是一种浏览器提供的接口,允许我们订阅性能时间线上的特定类型事件,当与 Performance API 结合使用时,它提供了一个全面的性能监控解决方案;
// 1. 创建 PerformanceObserver 实例,传入回调函数(核心:处理性能数据) const observer = new PerformanceObserver((list, observer) => { // list:性能数据列表(PerformanceObserverEntryList),包含所有触发的性能事件 // observer:当前 PerformanceObserver 实例(可用于停止监听、获取配置等) const perfEntries = list.getEntries(); // 获取所有性能条目 perfEntries.forEach(entry => { console.log('捕获到性能事件:', entry); // 示例:打印资源的名称、加载耗时、类型 console.log('资源名:', entry.name, '耗时:', entry.duration, '类型:', entry.entryType); }); }); // 2. 注册监听:指定要监听的性能指标类型(entryTypes) observer.observe({ entryTypes: ['resource'] }); // 3. 可选:不再需要时停止监听,释放资源 // observer.disconnect();
应用场景
-
首次内容绘制 (FCP):监控页面首次渲染的时间,帮助了解页面加载速度;
observer.observe({ entryTypes: ['paint'] }); -
自定义性能度量:使用 performance.mark() 和 performance.measure() 标记和测量自定义的时间点;
performance.mark('customStart'); // ... some code ... performance.mark('customEnd'); performance.measure('customMeasure', 'customStart', 'customEnd'); observer.observe({ entryTypes: ['measure'] });
案例
React✍️ 高阶组件以及组件补充
上一篇