2023-06-13
JavaScript
0
请注意,本文编写于 964 天前,最后修改于 713 天前,其中某些信息可能已经过时。

目录

MutationObserver
构造函数
实例方法
observe(el[, options])
disconnect()
takeRecords()
MutationRecord
应用场景
IntersectionObserver
构造函数
callback,
options
实例方法
IntersectionObserverEntry
应用场景
ResizeObserverEntry
应用场景

浏览器提供了 5 种 Observer 来监听DOM元素的变化:MutationObserver、IntersectionObserver、ResizeObserver、PerformanceObserver、ReportingObserver,其中比较常用的是前三个,能够很方便的实现一些特殊的效果。

MutationObserver

MutationObserver可以监听DOM树属性的变更,例如节点的增加、删除,属性的变更,以及文本内容的变更 MDN说明

构造函数

js
var observer = new MutationObserver(function (mutationRecord, observer) { // mutationRecord变动数组 // observer 观察者实例 });

实例方法

observe(el[, options])

启动监听,它接受两个参数。 第一个参数:所要观察的 DOM 节点 第二个参数:一个配置对象,指定所要观察的特定变动

配置对象参数如下:

  • childList:子节点的变动(指新增,删除或者更改)。
  • attributes:属性的变动。
  • characterData:节点内容或节点文本的变动。
  • subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
  • attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。
  • characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。
  • attributeFilter:数组,表示需要观察的特定属性(比如['class','src'])
js
// 开始监听文档根节点(即<html>标签)的变动 observer.observe(document.documentElement, { attributes: true, characterData: true, childList: true, subtree: true, attributeOldValue: true, characterDataOldValue: true });

如果某节点多次添加某观察器,回调函数只会触发一次,如果添加的options配置对象不相同,则会当做一个新的观察器,回调函数就会触发多个。

disconnect()

disconnect() 方法告诉观察者停止观察变动。可以通过调用其 observe() 方法来重用观察者。

takeRecords()

用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。

MutationRecord

MutationRecord 每个 MutationRecord 都代表一个独立的 DOM 变化,在每次随 DOM 变化调用 MutationObserver 的回调函数时,一个相应的 MutationRecord 会被作为参数,传递给回调函数。 MutationRecord对象包含了DOM的相关信息,有如下属性:

  • type:观察的变动类型(attribute、characterData或者childList)。
  • target:发生变动的DOM节点。
  • addedNodes:新增的DOM节点。
  • removedNodes:删除的DOM节点。
  • previousSibling:前一个同级节点,如果没有则返回null。
  • nextSibling:下一个同级节点,如果没有则返回null。
  • attributeName:发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。
  • oldValue:变动前的值。这个属性只对attribute和characterData变动有效,如果发生childList变动,则返回null。

应用场景

  1. 监听dom

  2. 执行微任务

    手动创建文本节点,再修改文本节点内容,触发MutationObserver执行回调函数

IntersectionObserver

IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。

当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

它的任务就是监听目标元素跟指定父元素(用户可指定,默认为viewport)是否在发生交叉行为,简单理解就是监听目标元素是否进入或者离开了指定父元素的内部

js
// 创建实例 const observer = new IntersectionObserver(callback, option); // 开始观察element1 observer.observe(element1); // 开始观察element2 observer.observe(element2); // 停止观察 observer.unobserve(element); // 关闭观察器 observer.disconnect();

构造函数

js
var observer = new IntersectionObserver(callback[, options]);

callback,

当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:

  • entries 一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。

  • observer 被调用的IntersectionObserver实例。

页面初始化的时候会触发一次callback,entries为所有已监听的目标集合。

注册的回调函数将会在主线程中被执行,所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法

options

一个可以用来配置 observer 实例的对象。 如果options未指定,observer 实例默认使用文档视口作为 root,并且没有 margin,阈值为 0%(意味着即使一像素的改变都会触发回调函数),可配置参数如下:

  • root 指定父元素,默认为视窗
  • rootMargin 触发交叉的偏移值,默认为"0px 0px 0px 0px"(上右下左,正数为向外扩散,负数则向内收缩)
  • threshold 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为 1.0,则意味着整个元素都在可见范围内时才算可见。阈值的默认值为 0.0。

如果设置rootMargin为"20px 0px 30px 30px",那么元素未到达视窗时,就已经切换为可见状态了:

clipboard-2023-06-13.png

实例方法

方法说明参数
observe开始监听一个目标元素dom节点
unobserve停止监听一个目标元素dom节点(要取消观察的目标节点)
takeRecords返回所有监听的目标元素集合
disconnect停止所有监听

IntersectionObserverEntry

IntersectionObserverEntry 的实例作为 entries 参数被传递到一个 IntersectionObserver 的回调函数中

属性说明
boundingClientRect目标元素的边界信息,与Element.getBoundingClientRect() 相同
intersectionRatio目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
intersectionRect目标元素与视口或根元素的交叉区域的信息
isIntersecting字面理解为是否正在交叉,可用做判断元素是否可见
target目标节点
time可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。

应用场景

  1. 懒加载
js
let images = document.querySelectorAll("img.lazyload"); let observer = new IntersectionObserver(entries => { entries.forEach(item => { if (item.isIntersecting) { item.target.src = item.target.dataset.origin; // 开始加载图片 observer.unobserve(item.target); // 停止监听已开始加载的图片 } }); }); images.forEach(item => observer.observe(item));
  1. 触底(加载更多)
html
<!-- 数据列表 --> <ul> <li>index</li> // 多个li </ul> <!-- 参照元素 --> <div class="reference"></div>
js
new IntersectionObserver(entries => { let item = entries[0]; if (item.isIntersecting) console.log("滚动到了底部,开始请求数据"); }).observe(document.querySelector(".reference")); // 监听参照元素
  1. 吸顶

    实现元素吸顶的方式有很多种,如css的position: sticky,兼容性较差;如果用交叉观察者实现也很方便,同样也要放一个参照元素

    html
    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <style type="text/css"> body { padding: 0; margin: 0; } #nav { width: 100%; height: 60px; background: #39f; color: #fff; line-height: 60px; text-align: center; padding: 0; margin: 0; list-style: none; } #nav li { float: left; width: 25%; height: 60px; } .wrap{ position: relative; } .reference{ position: absolute; } #nav.fixed { position: fixed; top: 0; left: 0; width: 100%; } </style> <body> <div class="wrap"> <h1>H5-学堂</h1> <p> HTML5学堂是一个热爱H5的讲师组成的组织,致力于构建一个前端、HTML5的分享平台,能够给学生提供一些资料,也为广大前端爱好者提供一个分享平台,其中涉及到的基本知识,JS底层知识,JS底层知识,面试真题、相关技术、未来发展等。 </p> <div class="reference"></div> <ul id="nav"> <li>HTML5学堂</li> <li>HTML5微博</li> <li>HTML5贴吧</li> <li>HTML5微信</li> </ul> <div class="con"> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> <p>告别“回到顶部”,如影随行的“吸顶式导航”</p> </div> </div> <script> let nav = document.querySelector("#nav"); let reference = document.querySelector(".reference"); reference.style.top = nav.offsetTop + "px"; new IntersectionObserver(entries => { let item = entries[0]; let top = item.boundingClientRect.top; // 当参照元素的的top值小于0,也就是在视窗的顶部的时候,开始吸顶,否则移除吸顶 if (top < 0) nav.classList.add("fixed"); else nav.classList.remove("fixed"); }).observe(reference); </script> </body> </html>

    也可以使用js 监听滚动事件来实现

    4.元素动画

    当元素出现时动态添加clsss实现特殊动画效果

    JS
    let list = document.querySelectorAll("ul li"); let observer = new IntersectionObserver(entries => { entries.forEach(item => { if (item.isIntersecting) { item.target.classList.add("show"); // 增加show类名 observer.unobserve(item.target); // 移除监听 } }); }); list.forEach(item => observer.observe(item));

    ResizeObserver

    ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化,用法与上面两个Observer类似。

js
const element1 = document.getElementById('div1'); const element2 = document.getElementById('div2'); /* * 新建以一个观察者,传入一个当尺寸发生变化时的回调处理函数 * 参数entries 是 ResizeObserverEntry 的数组,包含两个最重要的属性: * ResizeObserverEntry.contentRect 包含尺寸信息(x,y,width,height,top,right,left,bottom) * ResizeObserverEntry.target 目标对象,即当前观察到尺寸变化的对象 * */ const robserver = new ResizeObserver( (entries,observer) => { for (const entry of entries) { // 可以通过 判断 entry.target得知当前改变的 Element,分别进行处理。 switch(etry.target){ case element1 : entry.target.innerHTML = `第一个DIV尺寸 [${entry.contentRect.width} : ${entry.contentRect.height}]`; break; case element2 : entry.target.innerHTML = `第二个DIV尺寸 [${entry.contentRect.width} : ${entry.contentRect.height}]`; break; } } }); robserver.observe(element1); robserver.observe(element2);

ResizeObserverEntry

ResizeObserverEntry 接口是传递给 ResizeObserver() 构造函数中的回调函数参数的对象,它允许你获取真正在观察的 Element 或 SVGElement 最新的大小。

应用场景

1. 监视dom容器宽高的变化 例如配合echarts,实现图表的自适应
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:千寻

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!