IntersectionObserver:
一个元素从不可见到可见,从可见到不可见
??IntersectionObserver
是一种浏览器提供的 JavaScript API
,用于监测元素与视窗的交叉状态。它可以告诉开发者一个元素是否进入或离开视窗,以及两者的交叉区域的大小和位置。
它提供了一种高效的方法来观察元素是否进入或离开视窗,而无需依赖滚动事件或定时器。它可以通过回调函数及设定的阈值来实时地通知开发者目标元素与视窗的交叉状态,并根据需要采取相应的操作。
特性:
- 异步执行:
IntersectionObserver
是异步执行的,它使用浏览器的内部优化机制,不会阻塞主线程,从而避免了性能问题。 - 节省资源:相比于传统的滚动事件或定时器,
IntersectionObserver
可以精确地观察元素与视窗的交叉状态,避免了不必要的计算和回调触发,从而节省了资源的消耗。 - 多目标观察:
IntersectionObserver
可以同时观察多个目标元素,通过回调函数逐个通知开发者它们的交叉状态,方便进行批量操作。 - 自定义阈值:开发者可以设定一个或多个阈值,用来定义元素与视窗的交叉比例。当交叉比例超过或低于阈值时,会触发相应的回调函数。
API介绍:
- **IntersectionObserver(callback, options):**创建新的实例,传入回调函数和配置对象。
- **observe(target):**观察指定目标元素,传入目标元素。
- **unobserve(target):**停止观察指定目标元素。
- **disconnect():**停止观察,断开与所有目标元素的关联。
创建一个交叉观察器
通过调用 IntersectionObserver 构造函数,创建交叉观测器,并将回调函数传给它,当一个方向或另一个方向越过阈值时,就运行该函数:
let options = {root: document.querySelector("#scrollArea"),rootMargin: "0px",threshold: 1.0,
};let observer = new IntersectionObserver(callback, options);
阈值为 1.0 意味着目标元素完全出现在root
选项指定的元素中 100% 可见时,回调函数将会被执行。
IntersectionObserver 选项
传递到IntersectionObserver()构造函数的options
对象,可以控制在什么情况下调用观察器的回调。它有以下字段:
root
用作视口的元素,用于检查目标的可见性。必须是目标的祖先。如果未指定或为null
,则默认为浏览器视口。
rootMargin
根周围的边距。其值可以类似于 CSSmargin属性,例如"10px 20px 30px 40px"
(上、右、下、左)。这些值可以是百分比。在计算交叉点之前,这组值用于增大或缩小根元素边框的每一侧。默认值为全零。
threshold
一个数字或一个数字数组,表示目标可见度达到多少百分比时,观察器的回调就应该执行。如果只想在能见度超过 50% 时检测,可以使用 0.5 的值。如果希望每次能见度超过 25% 时都执行回调,则需要指定数组 [0, 0.25, 0.5, 0.75, 1]。默认值为 0(这意味着只要有一个像素可见,回调就会运行)。值为 1.0 意味着在每个像素都可见之前,阈值不会被认为已通过。
定位要观察的元素
创建一个观察器后,需要给定一个目标元素进行观察。
let target = document.querySelector("#listItem");
observer.observe(target);// 我们为观察器设置的回调将在第一次执行,
// 它将等待我们为观察器分配目标(即使目标当前不可见)
每当目标满足该IntersectionObserver
指定的阈值(threshold),回调被调用。回调接收IntersectionObserverEntry对象和观察器的列表:
let callback = (entries, observer) => {entries.forEach((entry) => {// 每个条目描述一个目标元素观测点的交叉变化:// entry.boundingClientRect// entry.intersectionRatio// entry.intersectionRect// entry.isIntersecting// entry.rootBounds// entry.target// entry.time});
};
其中,
entry
对象包括以下这些参数:**entry.boundingClientRect:**当前观察元素的矩形区域,top/right/bottom/left属性可以获得此时相对视区的距离,width/height属性包含尺寸。此属性和
Element.getBoundingClientRect()
这个API方法非常类似。**entry.intersectionRatio:**当前元素被交叉的比例。比例要想非常详细,需要
IntersectionObserver()
函数的第2个可选参数中设置thresholds
参数,也就是设置触发交叉事件的阈值。**entry.intersectionRect:**和视区交叉的矩形大小。
**entry.isIntersecting:**如果是true,则表示元素从视区外进入视区内。
**entry.rootBounds:**窗体根元素的矩形区域对象。
**entry.target:**当前交叉的元素。
entry.time:当前时间戳。
举个栗子
var zxxObserver = new IntersectionObserver(function (entries) {entries.forEach(function (entry) {if (entry.isIntersecting) {// entry.target元素进入区域了}});
});
// 观察元素1,2,...
zxxObserver.observe(ele1);
zxxObserver.observe(ele2);
...
用文字解释下就是这两步:
- 定义元素交叉后干嘛干嘛;
- 需要观察那些元素;
实际开发的时候,主要工作就是对entries.forEach
这部分的代码进行处理。
应用场景
探秘神奇的IntersectionObserver:释放网页性能的黑科技!IntersectionObserver 提供 - 掘金
图片懒加载
通过使用IntersectionObserver
,可以延迟加载图片,只在它们进入视窗时才开始加载。这样可以减少初始页面加载时间,并节省带宽和资源。
实现图片懒加载的步骤如下:
- 创建
IntersectionObserver
实例,并指定观察的目标元素。- 在回调函数中,判断目标元素是否进入视窗。
- 若目标元素进入视窗,将其真实的图片地址赋给元素的
src
属性,触发图片加载。+ 把所有需要延迟加载的图片用一个盒子包起来,设置宽高和默认占位图
+ 开始让所有IMG的SRC为空,把真实图片的地址放到IMG的自定义属性上,让IMG隐藏
+ 等到所有其他资源都加载完成后,再开始加载图片
+ 对于很多图片,需要当页面滚动的时候,当前图片区域显示出来后在加载真实图片
import { useEffect, useRef } from "react"
import styles from './LazyImg.module.scss'const Index = () => {const ImgRef = useRef<HTMLDivElement>(null)const handleLoad = (entries: IntersectionObserverEntry[]) => {entries.forEach(entry => {if (entry.isIntersecting) {const img = entry.target as HTMLImageElement;img.src = img.getAttribute("data-src") || ""; // 将真实的图片地址赋给 src 属性intersectionObserver.unobserve(img); // 停止观察该图片}})}const intersectionObserver = new IntersectionObserver(handleLoad)useEffect(() => {if (ImgRef.current) {const imgs = ImgRef.current.querySelectorAll('img');imgs.forEach(img => {intersectionObserver.observe(img)})}return () => {intersectionObserver.disconnect()}}, [])return <><div className={styles.main_box} ref={ImgRef}><img src="" alt="" data-src="../assets/module182_style1.png" /><img src="" alt="" data-src="../assets/module182_style2.png" /><img src="" alt="" data-src="../assets/module182_style1.png" /></div></>
}export default Index
用户兴趣埋点
规则是当某元素在视口停留时间达到2秒以上时,被认为用户对该元素感兴趣。
步骤如下:
- 创建
IntersectionObserver
实例,并指定观察的目标元素。- 在回调函数中,根据目标元素的交叉状态判断用户对该元素的兴趣。
- 根据规则判断适口停留时间是否满足条件。
- 如果满足条件,则记录兴趣数据或触发相应操作。
import { useEffect, useRef } from "react"const Index = () => {const BuriedRef = useRef<HTMLDivElement>(null)const handleBuried = (entries: IntersectionObserverEntry[]) => {entries.forEach(entry => {if (entry.isIntersecting && entry.intersectionRatio >= 0.5 &&entry.intersectionRect.width >= entry.boundingClientRect.width * 0.5 &&entry.intersectionRect.height >= entry.boundingClientRect.height * 0.5 &&entry.time >= 2000) {// 埋点操作}})}const intersectionObserver = new IntersectionObserver(handleBuried)useEffect(() => {if (BuriedRef.current) {const boxs = BuriedRef.current.querySelectorAll('item');boxs.forEach(box => { intersectionObserver.observe(box) })}return () => {intersectionObserver.disconnect()}}, [])return <div ref={BuriedRef}><div className="item"><img src="" alt="" data-src="../assets/module182_style1.png" /></div><div className="item"><img src="" alt="" data-src="../assets/module182_style2.png" /></div><div className="item"><img src="" alt="" data-src="../assets/module182_style1.png" /></div></div>
}export default Index
进阶
- 优化阈值设置
设置合适的交叉比例阈值可以减少不必要的回调函数触发。过多的阈值设置可能会导致频繁的回调函数执行,因此需要根据具体情况进行优化。 - 避免频繁的回调函数执行
由于IntersectionObserver
可能在短时间内多次触发回调函数,为了避免频繁的操作或网络请求,可以使用节流(throttling
)或防抖(debouncing
)技术进行处理。节流可以限制回调函数的执行频率,而防抖可以在指定时间内的连续触发中只执行最后一次。 - 优化性能与资源消耗
尽管IntersectionObserver
可以提供更好的性能,但当处理大量元素或复杂布局时,仍需考虑性能和资源消耗。可以结合使用时间间隔、限制最大触发次数等策略,确保在合理的范围内处理交叉状态变化。 - 控制监听范围
仅监听真正需要监测的元素,避免不必要的监听。过多的监听会增加性能消耗,并可能导致不必要的回调函数触发。 - 谨慎使用多个
IntersectionObserver
当需要监测多个元素时,使用多个IntersectionObserver
可能会增加代码复杂性和性能开销。在这种情况下,可以考虑合并监听逻辑,减少IntersectionObserver
的数量。 - 处理边界情况
注意处理边界情况,如元素尺寸变化、容器滚动等。在这些情况下,IntersectionObserver
可能无法及时检测到交叉状态的变化,需要进行额外的处理。 - 考虑兼容性
尽管大多数现代浏览器都支持IntersectionObserver
,但在一些旧版本浏览器中可能不被支持。为了确保兼容性,可以使用IntersectionObserver
的polyfill
或提供降级方案。 - 处理
IntersectionObserver
回调中的异步操作- 取消异步操作:在某些情况下,当元素离开视窗或不再需要异步操作时,可能需要取消正在进行的异步操作。例如,当用户迅速滚动页面时,可能需要取消之前触发的异步操作,以避免不必要的网络请求或计算。可以使用适当的方法,如取消
Promise
或中断正在进行的异步任务。 - 性能优化:对于耗时的异步操作,需要注意性能优化。考虑使用并发执行、缓存结果或其他优化策略,以减少延迟和资源消耗。
- 取消异步操作:在某些情况下,当元素离开视窗或不再需要异步操作时,可能需要取消正在进行的异步操作。例如,当用户迅速滚动页面时,可能需要取消之前触发的异步操作,以避免不必要的网络请求或计算。可以使用适当的方法,如取消
- 清理资源
当不再需要IntersectionObserver
监听或元素被销毁时,确保正确地清理和释放相关的资源。取消监听、解除绑定和清理回调函数,以避免内存泄漏和不必要的资源占用。 - 兼容性:尽管现代浏览器大多支持
IntersectionObserver
,但在一些旧版本的浏览器中可能不被支持。为了确保广泛的兼容性,开发者需要根据项目需求考虑是否需要提供降级方案或使用polyfill
。 - 事件顺序不确定性:由于
IntersectionObserver
是异步执行的,不同元素的回调函数执行顺序是不确定的。这可能会导致在处理相关逻辑时需要额外的注意,以确保正确的顺序和逻辑关联性。
MutationObserver
监听元素的属性和子节点的变化
??MutationObserver
的目标是解决传统的DOM
变化监听方式的局限性和性能问题,并提供更高效、灵活的DOM
变化监视机制。 在过去,开发人员使用DOM
事件监听或定时器轮询的方式来监视DOM
的变化。
MutationObserver用于监听DOM对象的变更,包括节点属性的变化、子节点的增删改等。提供了方便的方式监听DOM变化。
API介绍:
- **
MutationObserver(callback)
:**创建新的实例,传入变动时的回调函数。 - **
observe(target, config)
:**开始观察指定目标节点,传入目标节点和配置对象。 - **
disconnect()
:**停止观察,断开与所有目标节点的关联。 - takeRecords():从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中。
举个栗子
// 选择需要观察变动的节点
const targetNode = document.getElementById("some-id");// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };// 当观察到变动时执行的回调函数
const callback = function (mutationsList, observer) {// Use traditional 'for loops' for IE 11for (let mutation of mutationsList) {if (mutation.type === "childList") {console.log("A child node has been added or removed.");} else if (mutation.type === "attributes") {console.log("The " + mutation.attributeName + " attribute was modified.");}}
};// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);// 以上述配置开始观察目标节点
observer.observe(targetNode, config);// 之后,可停止观察
observer.disconnect();
??config
对象在MutationObserver
的observe
方法中用于指定哪些类型的 DOM 变动需要被观察。config
对象可以包含以下属性,每个属性都是一个布尔值,指示是否应该观察对应类型的变动:
attributes: 当被观察元素的属性变动时触发。注意,这个选项不会捕获
class
属性的变动,除非通过attributeNameFilter
明确指定。要捕获class
属性的变动,可以监视attributeFilter
为['class']
,或者使用子树变动(childList
)和特性变动(attributes
)的组合,因为类名的变化通常会导致子节点(如span
元素,用于通过 CSS 类应用样式)的添加或移除。childList: 当被观察元素的子节点变动时触发(添加或移除子节点)。
subtree: 当被观察元素的所有后代节点变动时触发。这个选项扩大了观察范围,使得变动不仅限于直接子节点,还包括所有更深层次的子节点。
characterData: 当被观察元素的文本内容变动时触发。这个选项仅对文本节点或设置了
nodeType
为Node.TEXT_NODE
的节点有效。注意,如果观察目标是元素节点,即使该元素包含文本节点,也不会触发此变动类型,除非这些文本节点是被单独观察的。characterDataOldValue: 当文本内容变动时,这个选项会被设置为
true
以捕获变动前的旧值。这个选项仅当characterData
也被设置为true
时有效。attributeOldValue: 当属性变动时,这个选项会被设置为
true
以捕获变动前的旧值。这个选项仅当attributes
也被设置为true
时有效。attributeFilter: 一个数组,包含需要观察变动的属性名称。只有当
attributes
设置为true
时,这个选项才有意义。只有指定的属性发生变动时,才会触发变动回调。
应用场景
开启DOM观察者模式,引爆你的前端开发创造力! - MutationObserverMutationObserver 的 - 掘金
白屏检测
// 创建 MutationObserver 实例
const observer = new MutationObserver((mutationsList) => {// 遍历每个 DOM 变化记录for (const mutation of mutationsList) {// 检查是否是节点的子节点发生了变化if (mutation.type === 'childList') {// 检查页面是否还有子节点if (document.body.childNodes.length === 0) {// 页面处于白屏状态console.log('页面处于白屏状态');} else {// 页面已经加载完成console.log('页面加载完成');}}}
});// 监视整个文档根节点的子节点变化
observer.observe(document.documentElement, {childList: true,subtree: true,
});// 停止监听
// observer.disconnect();
编辑器自动保存
??MutationObserver
在编辑器自动保存场景中可以用于监测编辑器内容的变化,从而实现自动保存功能。通过监听编辑器内容的变化,可以在用户输入或编辑内容时自动触发保存操作,避免用户因意外关闭页面或其他原因导致的数据丢失。
// 目标编辑器元素
const editor = document.getElementById('editor');// 创建 MutationObserver 实例
const observer = new MutationObserver((mutationsList) => {// 编辑器内容发生变化时触发保存操作saveEditorContent();
});// 监视编辑器内容的子节点变化
observer.observe(editor, { childList: true, subtree: true });// 保存编辑器内容的函数
function saveEditorContent() {// 执行保存操作,可以通过 Ajax 请求或其他方式将内容发送到服务器进行保存console.log('正在保存编辑器内容...');
}// 停止监听
// observer.disconnect();
防止水印被删除
? ?MutationObserver
可以用于防止水印被删除的场景,通过监测相关元素的变化,可以检测到水印元素是否被删除或修改,并及时进行恢复。
// 目标水印元素
const watermark = document.getElementById('watermark');// 创建 MutationObserver 实例
const observer = new MutationObserver(() => {// 水印元素发生变化时重新添加水印restoreWatermark();
});// 监视水印元素的父节点变化
observer.observe(watermark.parentNode, { childList: true });// 恢复水印的函数
function restoreWatermark() {// 检查水印元素是否存在,若不存在则重新添加if (!document.contains(watermark)) {// 重新添加水印到目标位置// ...console.log('水印被删除,已恢复');}
}// 停止监听
// observer.disconnect();
实时搜索和过滤
??MutationObserver
在实时搜索和过滤场景中可以用于监测搜索条件的变化,并在每次搜索条件发生变化时触发搜索或过滤操作。通过监听搜索条件的变化,可以及时响应用户的输入,并实时更新搜索结果或过滤列表,提供更好的搜索体验。
// 目标搜索输入框元素
const searchInput = document.getElementById('searchInput');// 创建 MutationObserver 实例
const observer = new MutationObserver(() => {// 搜索条件发生变化时执行搜索或过滤操作performSearch();
});// 监视搜索输入框的值变化
observer.observe(searchInput, { characterData: true, subtree: true });// 搜索或过滤操作的函数
function performSearch() {// 获取搜索输入框的值const searchValue = searchInput.value.trim();// 执行搜索或过滤操作,根据具体需求来实现// ...console.log('Performing search or filter: ' + searchValue);
}// 停止监听
// observer.disconnect();
进阶
- 精确指定目标:在创建
MutationObserver
实例时,明确指定要观察的目标元素,避免过于宽泛的监测范围。这可以提高性能并减少不必要的回调触发。 - 选择合适的观察选项:根据需求选择合适的观察选项。常见的选项包括
childList
(监听子节点的变化)、attributes
(监听属性的变化)、characterData
(监听文本节点内容的变化)等。根据实际情况,只选择需要监测的选项,避免监听不必要的变化。 - 使用
disconnect
方法停止监听:在不再需要监听的时候,调用disconnect
方法停止MutationObserver
的监听。这可以释放资源并避免不必要的回调触发。 - 避免频繁的回调触发:回调函数可能会在短时间内多次触发,尤其是在监测范围较大或有频繁变化的情况下。在回调函数中尽量避免执行耗时操作,以免影响性能。
- 结合其他技术和优化手段:
MutationObserver
可以与其他技术和优化手段结合使用,以实现更好的效果。例如,可以结合Debounce
或Throttle
技术来限制回调函数的触发频率,以减少频繁的回调。 - 浏览器兼容性考虑:
MutationObserver
在大多数现代浏览器中得到支持,但仍需注意浏览器的兼容性。如果需要在旧版本的浏览器中使用MutationObserver
,可以考虑使用polyfill
或其他替代方案。 - 回调函数执行时间:回调函数在每次
DOM
更新之后都会被触发,因此应尽量避免在回调函数中执行耗时操作,以免影响页面的响应性能。 - 无法监测样式变化:
MutationObserver
默认无法监测样式的变化,只能监测到DOM
结构的变化。如果需要监测样式的变化,可以使用CSSOM
或其他技术进行检测。 - 无法监测属性的初始值变化:
MutationObserver
只能监测到属性的后续变化,而无法监测到属性初始值的变化。如果需要监测属性初始值的变化,可以通过其他方法进行检测,如在元素创建之前记录初始值。 - 可能触发多次回调:在某些情况下,
MutationObserver
可能会在短时间内触发多次回调,尤其是当监测范围较大或有频繁的DOM
变化时。因此,在回调函数中应考虑回调的频率和性能消耗,避免执行过多的耗时操作。 - 无法跨域监测和操作:由于浏览器的安全策略限制,
MutationObserver
无法跨域监测和操作DOM
。只能在同域的情况下使用MutationObserver
进行DOM
监测。 - 不支持
IE9
及以下版本:MutationObserver
不支持IE9
及以下版本的浏览器,如果需要在旧版本的浏览器中使用MutationObserver
,可以考虑使用polyfill
或其他替代方案。 - 注意 DOM 修改的影响:在回调函数中对 DOM 进行修改可能会触发新的
DOM
变化,从而再次触发MutationObserver
的回调函数。这可能导致无限循环的情况发生,因此在修改DOM
时要小心谨慎。
ResizeObserver
**??ResizeObserver
**接口监视Element内容盒或边框盒或者SVGElement边界尺寸的变化。
同时无需再手动调用
getBoundingClientRect
来获取元素的尺寸大小,它对任何元素大小变化做出反应,与引起变化的原因无关。通知的内容包含了足够的信息,以便开发者能够根据当前元素的具体大小信息来作出变化,而不是要开发者重新调用getComputedStyle、getBoundingClientRect来获取。
- 监听元素:target。
- contentRect。
- contentBoxSize。
- borderBoxSize。
- devicePixelContentBoxSize。
需要注意的是,虽然只有当 BoxOptions 关心的盒模型变化时才会触发通知,但实际上通知时会将三种不同盒模型下的具体大小都返回给回调函数,用户无需再次手动获取。
window.resize事件能帮我们监听窗口大小的变化。但是reize事件会在一秒内触发将近60次,所以很容易在改变窗口大小时导致性能问题。换句话说,window.resize事件通常是浪费的,因为它会监听每个元素的大小变化(只有window对象才有resize事件),而不是具体到某个元素的变化。如果我们只想监听某个元素的变化的话,这种操作就很浪费性能了。
而ResizeObserver API就可以帮助我们:监听一个DOM节点的变化,这种变化包括但不仅限于:
- 某个节点的出现和隐藏
- 某个节点的大小变化
resize事件只有当 viewport 的大小发生变化时会被触发,元素大小的变化不会触发 resize 事件;并且也只有注册在 window 对象上的回调会在 resize 事件发生时被调用,其他元素上的回调不会被调用。
当「resize」事件发生后,我们往往需要通过调用getBoundingClientRect或者getComputedStyle来获取此时我们关心的元素大小,以此判断元素是否发生了变化。频繁调用getBoundingClientRect、getComputedStyle等 API 会导致「浏览器重排(reflow)」,导致页面性能变差,
API介绍:
- **ResizeObserver.disconnect():**取消特定观察者目标上所有对Element的监听。
- **ResizeObserver.observe():**开始对指定Element的监听。
- 第一个参数为观察的元素。
- 第二个参数为可选参数 BoxOptions,用来指定将以哪种盒子模型来观察变动,如content-box(默认值),border-box和device-pixel-content-box。
- **ResizeObserver.unobserve():**结束对指定Element的监听。
第三方 --resize-observer-polyfill
栗子:使用 resize-observer-polyfill 管理 DOM 变化-JavaScript中文网-JavaScript教程资源分享门户
推荐开源项目:ResizeObserver Polyfill - 窗口尺寸监听解决方案-CSDN博客
举个栗子
const ro = new ResizeObserver(entries => {for (let entry of entries) {entry.target.style.borderRadius = Math.max(0, 250 - entry.contentRect.width) + 'px';}
});ro.observe(document.querySelector('.box'));
应用场景
虚拟列表支持动态高度
响应式广告投放
探索 ResizeObserver 的神奇力量ResizeObserver API 的目标是提供一种高效且准确地监听 D - 掘金
PerformanceObserver
待补充…
参考文献:
探秘神奇的IntersectionObserver:释放网页性能的黑科技!IntersectionObserver 提供 - 掘金
交叉观察器 API - Web API | MDN
尝试使用JS IntersectionObserver让标题和导航联动 ? 张鑫旭-鑫空间-鑫生活
交叉观察器 API - Web API | MDN
JavaScript中最重要的5个Observer,看这一篇就够了_js observer-CSDN博客
开启DOM观察者模式,引爆你的前端开发创造力! - MutationObserverMutationObserver 的 - 掘金
MutationObserver - Web API | MDN
ResizeObserver - Web API | MDN
Resize Observer 介绍及原理浅析-resize-observer-polyfill
一个较新的WEB API——ResizeObserver 的使用今天在看同事代码的时候看见这个API,出于好奇就去了解了 - 掘金
浏览器的 5 种 Observer,你用过几种?网页开发中我们经常要处理用户交互,我们会用 addEventListen - 掘金