MutationObserver

基本使用

  1. MutationObserver 提供了监听 DOM 树变化的能力,可以追踪 DOM 树的变化,包括节点的增加、删除、属性的修改等;
  2. 它是异步的,这意味着它会将所有的 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. 场景 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. 场景 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. 场景 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. 场景 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

基本使用

  1. IntersectionObserver 是一个现代的浏览器 API,用于监听目标元素与根元素 / 视口的交叉状态变化 (简单说:目标元素是否进入 / 离开可视区域,或与根元素有重叠)

  2. 它非常适合实现图片懒加载、无限滚动、广告曝光率等功能;

    // 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. 图片懒加载:当图片元素即将进入视口时,触发加载该图片资源,从而优化页面加载速度和减少不必要的带宽消耗;

    // 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);
    });
    
  2. 无线滚动加载:在用户滚动页面接近底部时,可以用来实现更多内容的动态加载;

    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);
    
  3. 广告跟踪与统计:监测广告是否被用户实际看到,以便进行广告曝光量的计算和计费;

  4. 视频或音频自动播放:当媒体元素进入视口时,开始播放视频或音频,避免页面载入时就播放多个媒体导致资源浪费;

  5. 交互式视差滚动效果:根据元素在视口中的位置变化,动态调整其 CSS 样式,创建深度感强烈的滚动动画效果;

  6. 性能优化:对不在视口内的组件或区块进行渲染优化,比如使用骨架屏代替未加载的内容,待其进入视口时再渲染完整内容;

案例:元素曝光动效

ResizeObserver

基本使用

  1. ResizeObserver 是浏览器原生提供的异步观察 API,用于监听 DOM 元素的尺寸变化 (包括宽高、边框、内边距等布局尺寸),也能监听元素的容器尺寸变化;

  2. 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();
    

应用场景

  1. 响应式布局:当网页布局需要根据容器大小变化而自适应时,可以使用 ResizeObserver 来监听容器元素的大小变化,并相应地调整布局和样式;

  2. 数据可视化:在使用如 ChartJs 等图表库创建图表时,可以结合 ResizeObserver 来实现图表的自适应大小;

  3. 富文本编辑器:在富文本编辑器中,内容的变化可能导致滚动条和布局的调整;

  4. 拖放功能:在实现拖放功能时,拖动元素可能会改变目标区域的尺寸,可以实时更新目标区域的尺寸,以适应拖动的元素,并触发相应的操作;

  5. 图片和媒体元素的自适应:当网页中包含图片、视频或其他媒体元素时,可以使用 ResizeObserver 来监听其容器元素的大小变化,自动调整媒体元素的大小和布局,确保媒体元素在不同设备和屏幕尺寸下都能正确显示;

案例:尺寸监听

PerformanceObserver

基本使用

  1. 在前端性能分析领域,了解和监控网页的运行性能至关重要,PerformanceObserver API 供了一个强大的工具来监听性能时间线上的事件,从而实现更细粒度的性能监控;

  2. 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();
    

应用场景

  1. 首次内容绘制 (FCP):监控页面首次渲染的时间,帮助了解页面加载速度;

    observer.observe({ entryTypes: ['paint'] });
    
  2. 自定义性能度量:使用 performance.mark()performance.measure() 标记和测量自定义的时间点;

    performance.mark('customStart');
    // ... some code ...
    performance.mark('customEnd');
    performance.measure('customMeasure', 'customStart', 'customEnd');
    observer.observe({ entryTypes: ['measure'] });
    

案例

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. MutationObserver
    1. 1.1. 基本使用
    2. 1.2. 应用场景
    3. 1.3. 案例:DOM 变化监控面板
  2. 2. IntersectionObserver
    1. 2.1. 基本使用
    2. 2.2. 应用场景
    3. 2.3. 案例:元素曝光动效
  3. 3. ResizeObserver
    1. 3.1. 基本使用
    2. 3.2. 应用场景
    3. 3.3. 案例:尺寸监听
  4. 4. PerformanceObserver
    1. 4.1. 基本使用
    2. 4.2. 应用场景
    3. 4.3. 案例