MutationObserver
- MutationObserver 提供了监听 DOM 树变化的能力,可以追踪 DOM 树的变化,包括节点的增加、删除、属性的修改等;
- 它是异步的,这意味着它会将所有的 DOM 变化集合起来一次性报告,而不是在每一次变化后立刻报告;
基本使用
const observer = new MutationObserver((mutations, observer) => {
mutations.forEach((mutation) => {
// type: 观察的变动类型(attribute、characterData 或者 childList)
// target: 发生变动的 DOM 节点
// addedNodes: 新增的 DOM 节点
// removedNodes: 删除的 DOM 节点
// previousSibling: 前一个同级节点,如果没有则返回 null
// nextSibling: 下一个同级节点,如果没有则返回 null
// attributeName: 发生变动的属性,如果设置了attributeFilter,则只返回预先指定的属性
// oldValue: 变动前的值,这个属性只对 attribute 和 characterData 变动有效,如果发生 childList 变动,则返回null
console.log(mutation.type);
});
});
// 传入目标节点和观察选项
observer.observe(document.body, {
attributes: true, // 监听目标节点的属性变化,例如 id,class 等属性
childList: true, // 除目标节点外还要监听目标节点的直接子节点,增加或删除变化
subtree: true, // 监听目标节点以及所有后代的变化
characterData: true, // 监听目标节点的文本内容或字符数据变化
attributeOldValue: true, // 观察 attributes 变动时,是否需要记录变动前的属性值
characterDataOldValue: true, // 观察characterData变动时,是否需要记录变动前的值
attributeFilter: ['class', 'src'], // 需要观察的特定属性
});
// 停止观察
// observer.disconnect();
// 清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组
// observer.takeRecords()
应用场景
-
自动保存表单数据;
-
图片懒加载;
-
检测 DOM / 属性 变化并做出响应;
案例:todo list
<!DOCTYPE html>
<html>
<head>
<title>MutationObserver To-Do List Demo</title>
<style>
#todo-list {
list-style-type: none;
}
</style>
</head>
<body>
<h1>待办事项列表</h1>
<ul id="todo-list">
<li>完成作业</li>
<li>购物</li>
</ul>
<button id="addTask">添加任务</button>
<button id="removeTask">移除任务</button>
<p id="taskCount">任务数量:2</p>
<script>
const todoList = document.getElementById("todo-list");
const taskCount = document.getElementById("taskCount");
const observer = new MutationObserver((mutationsList, observer) => {
console.log(mutationsList);
mutationsList.forEach((mutation) => {
if (mutation.type === "childList") {
updateTaskCount();
}
});
});
observer.observe(todoList, { childList: true });
document.getElementById("addTask").addEventListener("click", () => {
const newTask = document.createElement("li");
newTask.textContent = "新任务";
todoList.appendChild(newTask);
});
document.getElementById("removeTask").addEventListener("click", () => {
const tasks = todoList.getElementsByTagName("li");
if (tasks.length > 0) {
todoList.removeChild(tasks[0]);
}
});
function updateTaskCount() {
const tasks = todoList.getElementsByTagName("li");
taskCount.textContent = `任务数量:${tasks.length}`;
}
</script>
</body>
</html>
IntersectionObserver
IntersectionObserver 是一个现代的浏览器 API,允许开发者在某个元素与其祖先元素或顶层文档视口发生交叉时得到通知;
它非常适合实现图片懒加载、无限滚动、广告曝光率等功能;
基本使用
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
/*
// 被监听 DOM 元素的 Rect 信息
boundingClientRect: {
bottom: 208
height: 200
left: 8
right: 208
top: 8
width: 200
x: 8
y: 8
}
intersectionRatio: 1 // 交叉比例
// 被监听元素与 Root 元素交叉部分矩形的 Rect 信息
intersectionRect: {
bottom: 208,
height: 200,
left: 8,
right: 208,
top: 8,
width: 200,
x: 8,
y: 8
},
// 是否处于交叉状态
isIntersecting: true,
isVisible: false,
// Root 元素的 Rect 信息
rootBounds: {
bottom: 606,
height: 606,
left: 0,
right: 476,
top: 0,
width: 476,
x: 0,
y: 0
},
// root 元素
target: div#target,
time: 49.09999990463257
*/
console.log(entry);
if (entry.isIntersecting) { // 判断目标元素是否在视口中
console.log('目标元素在视口中!');
} else {
console.log('目标元素不在视口中.');
}
});
},
{
root: document.querySelector('.scroll-container'), // 观察目标的父元素,如果不设置,默认为浏览器视口
rootMargin: '10px', // 增加或减少观察目标的可见区域大小
threshold: [0, 0.25, 0.5, 0.75, 1] // 当观察目标的可见比例达到这些阈值时会触发回调函数
}
);
// 开始观察某个元素
const targetElement = document.querySelector('.some-class');
observer.observe(targetElement);
// 停止观察某个元素
observer.unobserve(targetElement);
// 停止观察所有元素
observer.disconnect();
应用场景
-
图片懒加载:当图片元素即将进入视口时,触发加载该图片资源,从而优化页面加载速度和减少不必要的带宽消耗;
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.lazy; observer.unobserve(img); } }); },{ rootMargin: '100px' }); document.querySelectorAll('img[data-lazy]').forEach(img => { observer.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 样式,创建深度感强烈的滚动动画效果;
-
性能优化:对不在视口内的组件或区块进行渲染优化,比如使用骨架屏代替未加载的内容,待其进入视口时再渲染完整内容;
案例:动态展示导航的阴影
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sticky Header with Shadow on Intersection</title>
<style>
body {
margin: 0;
padding: 0;
}
header {
height: 80px;
background-color: #3498db;
color: white;
text-align: center;
line-height: 80px;
position: sticky;
top: 0;
z-index: 100;
}
.header-shadow {
transition: box-shadow 0.3s ease;
}
.header-shadow.shadow {
box-shadow: 0 2px 5px black;
}
section {
height: 1000px;
background-color: #ecf0f1;
padding: 20px;
}
</style>
</head>
<body>
<div id="guard"></div>
<header id="sticky-header" class="header-shadow">Sticky Header</header>
<section>
<p>向下滚动触发 sticky 时展示 shadow</p>
</section>
<script>
const header = document.getElementById("sticky-header");
const section = document.querySelector("section");
const options = {
threshold: 1,
};
// guard 滚动到可视区域以外时认为触发了 shadow
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
header.classList.remove("shadow");
} else {
header.classList.add("shadow");
}
});
}, options);
intersectionObserver.observe(document.getElementById("guard"));
</script>
</body>
</html>
ResizeObserver
ResizeObserver 它允许开发者监听元素的尺寸变化,在前端开发中,元素尺寸的变化可能会受到许多因素的影响,例如窗口大小调整、设备方向变化、内部内容变化等;
ResizeObserver 提供了一种高效的方法来响应这些变化,而不需要频繁使用事件监听器或轮询技术;
基本使用
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
/*
{
// 不同box-sizing下的尺寸
borderBoxSize: [{
blockSize: 200,
inlineSize: 200,
}],
contentBoxSize: [{
blockSize: 200,
inlineSize: 200,
}],
contentRect: {
bottom: 200,
height: 200,
left: 0,
right: 200,
top: 0,
width: 200,
x: 0,
y: 0
},
// 在物理设备像素上的大小, 在不同的屏幕上尺寸不同例如Retina
devicePixelContentBoxSize: [{
blockSize: 300,
inlineSize: 300
}
],
target: div#resizable-box
}
*/
console.log(entry);
const { width, height } = entry.contentRect;
console.log(`Element size: ${width} x ${height}`);
}
});
// 开始监听目标元素
const targetElement = document.querySelector('.resize-me');
ro.observe(targetElement);
// 监听多个元素
// const targetElements = document.querySelectorAll('.resize-me');
// targetElements.forEach(elem => {
// ro.observe(elem);
// });
// 停止监听
// ro.unobserve(targetElement);
应用场景
-
响应式布局:当网页布局需要根据容器大小变化而自适应时,可以使用 ResizeObserver 来监听容器元素的大小变化,并相应地调整布局和样式;
-
数据可视化:在使用如 ChartJs 等图表库创建图表时,可以结合 ResizeObserver 来实现图表的自适应大小;
-
富文本编辑器:在富文本编辑器中,内容的变化可能导致滚动条和布局的调整;
-
拖放功能:在实现拖放功能时,拖动元素可能会改变目标区域的尺寸,可以实时更新目标区域的尺寸,以适应拖动的元素,并触发相应的操作;
-
图片和媒体元素的自适应:当网页中包含图片、视频或其他媒体元素时,可以使用 ResizeObserver 来监听其容器元素的大小变化,自动调整媒体元素的大小和布局,确保媒体元素在不同设备和屏幕尺寸下都能正确显示;
案例:拖动放大缩小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ResizeObserver Demo with Resizable Box</title>
<style>
#resizable-box {
width: 200px;
height: 200px;
background-color: #3498db;
color: white;
text-align: center;
line-height: 200px;
font-size: 24px;
transition: background-color 0.5s ease;
resize: both;
overflow: auto;
cursor: pointer;
}
</style>
</head>
<body>
<div id="resizable-box">Resize me!</div>
<script>
const resizableBox = document.getElementById("resizable-box");
let isResizing = false;
let startX, startY, startWidth, startHeight;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log("宽度:", width, "高度:", height);
}
});
resizeObserver.observe(resizableBox);
resizableBox.addEventListener("mousedown", startResize);
document.addEventListener("mousemove", handleResize);
document.addEventListener("mouseup", stopResize);
function startResize(e) {
isResizing = true;
startX = e.clientX;
startY = e.clientY;
startWidth = parseInt(
document.defaultView.getComputedStyle(resizableBox).width,
10
);
startHeight = parseInt(
document.defaultView.getComputedStyle(resizableBox).height,
10
);
}
function handleResize(e) {
if (!isResizing) return;
const newWidth = startWidth + (e.clientX - startX);
const newHeight = startHeight + (e.clientY - startY);
resizableBox.style.width = newWidth + "px";
resizableBox.style.height = newHeight + "px";
}
function stopResize() {
isResizing = false;
}
</script>
</body>
</html>
PerformanceObserver
在前端性能分析领域,了解和监控网页的运行性能至关重要,PerformanceObserver API 供了一个强大的工具来监听性能时间线上的事件,从而实现更细粒度的性能监控;
PerformanceObserver 是一种浏览器提供的接口,允许我们订阅性能时间线上的特定类型事件,当与 Performance API 结合使用时,它提供了一个全面的性能监控解决方案;
基本使用
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
}
});
/*
常见的 entryTypes:
mark 用于标记时间戳的事件
measure performance.measure 触发的事件
frame 网页渲染的事件
navigation 导航的事件,例如页面加载或重新加载
resource 资源加载事件
longtask 长任务事件
paint 绘制事件,例如 FP,FCP
layout-shift 用于监视布局变化的事件
*/
observer.observe({ entryTypes: ['paint', 'measure'] });
应用场景
-
首次内容绘制 (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✍️ 高阶组件以及组件补充
上一篇