浏览器提供了 5 种 Observer 来监听DOM元素的变化:MutationObserver、IntersectionObserver、ResizeObserver、PerformanceObserver、ReportingObserver,其中比较常用的是前三个,能够很方便的实现一些特殊的效果。
MutationObserver可以监听DOM树属性的变更,例如节点的增加、删除,属性的变更,以及文本内容的变更 MDN说明
jsvar observer = new MutationObserver(function (mutationRecord, observer) {
// mutationRecord变动数组
// observer 观察者实例
});
启动监听,它接受两个参数。 第一个参数:所要观察的 DOM 节点 第二个参数:一个配置对象,指定所要观察的特定变动
配置对象参数如下:
js// 开始监听文档根节点(即<html>标签)的变动
observer.observe(document.documentElement, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
如果某节点多次添加某观察器,回调函数只会触发一次,如果添加的options配置对象不相同,则会当做一个新的观察器,回调函数就会触发多个。
disconnect() 方法告诉观察者停止观察变动。可以通过调用其 observe() 方法来重用观察者。
用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
MutationRecord 每个 MutationRecord 都代表一个独立的 DOM 变化,在每次随 DOM 变化调用 MutationObserver 的回调函数时,一个相应的 MutationRecord 会被作为参数,传递给回调函数。 MutationRecord对象包含了DOM的相关信息,有如下属性:
监听dom
执行微任务
手动创建文本节点,再修改文本节点内容,触发MutationObserver执行回调函数
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();
jsvar observer = new IntersectionObserver(callback[, options]);
当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:
entries 一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
observer 被调用的IntersectionObserver实例。
页面初始化的时候会触发一次callback,entries为所有已监听的目标集合。
注册的回调函数将会在主线程中被执行,所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法
一个可以用来配置 observer 实例的对象。 如果options未指定,observer 实例默认使用文档视口作为 root,并且没有 margin,阈值为 0%(意味着即使一像素的改变都会触发回调函数),可配置参数如下:
如果设置rootMargin为"20px 0px 30px 30px",那么元素未到达视窗时,就已经切换为可见状态了:

| 方法 | 说明 | 参数 |
|---|---|---|
| observe | 开始监听一个目标元素 | dom节点 |
| unobserve | 停止监听一个目标元素 | dom节点(要取消观察的目标节点) |
| takeRecords | 返回所有监听的目标元素集合 | |
| disconnect | 停止所有监听 |
IntersectionObserverEntry 的实例作为 entries 参数被传递到一个 IntersectionObserver 的回调函数中
| 属性 | 说明 |
|---|---|
| boundingClientRect | 目标元素的边界信息,与Element.getBoundingClientRect() 相同 |
| intersectionRatio | 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0 |
| intersectionRect | 目标元素与视口或根元素的交叉区域的信息 |
| isIntersecting | 字面理解为是否正在交叉,可用做判断元素是否可见 |
| target | 目标节点 |
| time | 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。 |
jslet 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));
html<!-- 数据列表 -->
<ul>
<li>index</li> // 多个li
</ul>
<!-- 参照元素 -->
<div class="reference"></div>
jsnew IntersectionObserver(entries => {
let item = entries[0];
if (item.isIntersecting) console.log("滚动到了底部,开始请求数据");
}).observe(document.querySelector(".reference")); // 监听参照元素
吸顶
实现元素吸顶的方式有很多种,如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 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化,用法与上面两个Observer类似。
jsconst 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 接口是传递给 ResizeObserver() 构造函数中的回调函数参数的对象,它允许你获取真正在观察的 Element 或 SVGElement 最新的大小。
1. 监视dom容器宽高的变化 例如配合echarts,实现图表的自适应


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