文章目录
- ResizeObserver、MutationObserver和IntersectionObserver
- 用MutationObserver实现图片懒加载
- MutationObserver 兼容性问题
- IntersectionObserver 应用
- MutationObserver和IntersectionObserver的区别
- IntersectionObserver 实例
- 示例一:图片懒加载
- 示例二:无限滚动加载更多内容
- 示例三:元素可视性跟踪及动画触发
ResizeObserver、MutationObserver和IntersectionObserver
- ResizeObserver
- 定义与用途
- ResizeObserver是一个JavaScript API,用于观察元素大小的变化。它提供了一种高效的方式来检测元素的大小何时发生改变,包括元素的宽度、高度、边框等尺寸相关属性的变化。这在响应式布局、动态调整元素尺寸以及根据元素大小变化执行相应操作等场景中非常有用。
- 工作原理
- 当你创建一个ResizeObserver实例,并传入一个回调函数后,这个实例就开始观察指定的目标元素。每当目标元素的大小发生变化时,回调函数就会被调用。回调函数会接收到一个包含所有被观察元素的数组作为参数,每个元素对应的是一个ResizeObserverEntry对象。这个对象包含了元素的新尺寸信息,如
contentRect
属性,它提供了元素内容区域的尺寸(包括宽度、高度、左上角坐标等)。
- 当你创建一个ResizeObserver实例,并传入一个回调函数后,这个实例就开始观察指定的目标元素。每当目标元素的大小发生变化时,回调函数就会被调用。回调函数会接收到一个包含所有被观察元素的数组作为参数,每个元素对应的是一个ResizeObserverEntry对象。这个对象包含了元素的新尺寸信息,如
- 示例代码
const targetElement = document.getElementById('myElement'); const resizeObserver = new ResizeObserver((entries) => {for (let entry of entries) {console.log('Element size changed:', entry.contentRect);// 在这里可以根据元素新的尺寸进行操作,比如重新布局其他元素} }); resizeObserver.observe(targetElement);
- 优势
- 性能高效:它能够在不使用轮询的情况下,精确地捕获元素大小变化,减少了不必要的计算和资源消耗。相比传统的通过定时器不断检查元素大小的方法,性能上有很大提升。
- 精准监测:能够准确地提供发生变化的元素以及变化后的尺寸信息,方便开发者针对具体的变化做出精准的响应。
- 局限性
- 兼容性问题:在一些旧版本的浏览器中可能不被支持,需要进行适当的浏览器兼容性处理,比如使用polyfill来确保在不支持的浏览器中也能有类似的功能。
- 只关注尺寸变化:它仅仅关注元素的尺寸相关变化,对于其他类型的变化(如元素内容的改变、属性的改变等)无法进行观察。
- 定义与用途
- MutationObserver
- 定义与用途
- MutationObserver是一个用于监视DOM树变化的JavaScript API。它可以观察目标节点(或其后代节点)的添加、删除、属性修改等多种类型的变化。这在需要动态更新页面内容、响应DOM结构变化以及实现类似数据绑定等功能时非常有用。
- 工作原理
- 首先创建一个MutationObserver实例,并传入一个回调函数。这个回调函数会在DOM发生符合观察条件的变化时被调用。然后,通过
observe
方法指定要观察的目标节点和观察选项(如观察子节点的添加或删除、属性变化等)。当DOM变化发生时,回调函数会收到一个包含所有变化记录的数组(MutationRecord对象组成)作为参数,每个MutationRecord对象描述了一个具体的DOM变化,包括变化类型(如childList
表示子节点变化,attributes
表示属性变化等)、变化涉及的节点等信息。
- 首先创建一个MutationObserver实例,并传入一个回调函数。这个回调函数会在DOM发生符合观察条件的变化时被调用。然后,通过
- 示例代码
const targetNode = document.getElementById('myContainer'); const mutationObserver = new MutationObserver((mutationsList) => {for (let mutation of mutationsList) {if (mutation.type === 'childList') {console.log('Child nodes have been added or removed.');} else if (mutation.type === 'attributes') {console.log('An attribute has been modified.');}// 根据具体的变化类型进行相应的操作} }); const config = { attributes: true, childList: true, subtree: true }; mutationObserver.observe(targetNode, config);
- 优势
- 广泛的DOM变化监测:可以观察多种DOM变化,包括节点的添加、删除、属性修改、文本内容变化等,提供了全面的DOM变化监控能力。
- 异步回调机制:它采用异步方式处理变化回调,这意味着多个DOM变化可以在一次回调中处理,避免了频繁触发回调导致的性能问题,使得在处理大量DOM变化时更加高效。
- 局限性
- 复杂的API和概念:对于初学者来说,理解和正确使用MutationObserver的API(如配置选项、MutationRecord对象的结构等)可能有一定的难度。
- 无法阻止默认行为:它主要用于观察DOM变化,不能直接阻止DOM变化的发生,例如不能阻止节点的删除或属性的修改等默认行为。
- 定义与用途
用MutationObserver实现图片懒加载
- 理解懒加载原理
- 懒加载是一种延迟加载图片的技术。当页面初始化时,不加载所有的图片,而是等到图片进入浏览器的可视区域时才加载。这样可以减少页面初始加载的时间和资源消耗,特别是在页面有大量图片的情况下非常有用。
- 使用MutationObserver实现懒加载的步骤
- 步骤一:HTML结构准备
- 首先,在HTML中,将需要懒加载的图片的
src
属性设置为一个占位符(比如一个很小的loading图片或者一个空白图片),同时添加一个自定义属性(例如data-src
)来存储真实的图片源地址。 - 示例代码如下:
<img src="placeholder.jpg" data - src="real-image.jpg" alt="懒加载图片">
- 首先,在HTML中,将需要懒加载的图片的
- 步骤二:JavaScript代码实现
- 创建MutationObserver实例并配置观察选项
- 创建一个
MutationObserver
对象,传入一个回调函数,用于处理观察到的DOM变化。同时,配置观察选项,一般需要观察childList
(子节点变化)和attributes
(属性变化),因为图片可能会因为各种原因(如插入新节点、修改样式使图片进入可视区等)而需要加载。 - 示例代码如下:
const observer = new MutationObserver(callback); const config = {childList: true,attributes: true };
- 创建一个
- 开始观察目标元素
- 选择包含懒加载图片的父元素作为观察目标。这可以是整个文档(
document.documentElement
),也可以是某个特定的容器元素,如document.getElementById('image - container')
。然后使用observer.observe
方法开始观察目标元素,传入目标元素和配置选项。 - 示例代码如下:
const targetElement = document.body; // 假设图片在body内,可根据实际情况修改 observer.observe(targetElement, config);
- 选择包含懒加载图片的父元素作为观察目标。这可以是整个文档(
- 定义回调函数来处理DOM变化和加载图片
- 在回调函数中,遍历所有观察到的变化记录(
mutations
)。对于每个变化记录,检查变化的类型。如果是childList
类型的变化,遍历新增的节点,检查节点是否为图片元素并且尚未加载(通过检查src
属性是否为占位符)。如果是attributes
类型的变化,检查被修改属性的元素是否为图片元素并且尚未加载。 - 对于需要加载的图片,获取其
data - src
属性的值作为真实图片源地址,将其赋值给src
属性来触发图片加载。同时,可以添加一些加载动画或者其他加载提示的处理。 - 示例代码如下:
function callback(mutations) {mutations.forEach((mutation) => {if (mutation.type === 'childList') {const addedNodes = mutation.addedNodes;addedNodes.forEach((node) => {if (node.tagName === 'IMG' && node.getAttribute('src')=== 'placeholder.jpg') {const rect = node.getBoundingClientRect();if (rect.top < window.innerHeight && rect.bottom >= 0) {const realSrc=node.getAttribute('data - src');node.setAttribute('src', realSrc);// 可以在这里添加加载动画相关代码,如移除加载提示等}}});} else if (mutation.type === 'attributes') {const target = mutation.target;if (target.tagName === 'IMG' && target.getAttribute('src') === 'placeholder.jpg') {const rect = target.getBoundingClientRect();if (rect.top < window.innerHeight && rect.bottom >= 0){const realSrc = target.getAttribute('data-src');target.setAttribute('src', realSrc);// 可以在这里添加加载动画相关代码,如移除加载提示等}}}}); }
- 在回调函数中,遍历所有观察到的变化记录(
- 创建MutationObserver实例并配置观察选项
- 步骤一:HTML结构准备
- 注意事项
- 浏览器兼容性:
MutationObserver
在大部分现代浏览器中都得到了支持,但在一些旧浏览器中可能不支持。可以考虑使用polyfill来确保兼容性。 - 性能优化:频繁地检查图片是否进入可视区可能会消耗一定的性能。可以考虑使用节流(throttle)或者防抖(debounce)技术来减少不必要的检查次数。例如,只有在滚动事件停止后的一段时间内或者每隔一定时间才进行图片是否进入可视区的检查。
- 错误处理:在获取和设置图片
src
属性时,可能会出现网络错误或者图片资源不存在的情况。需要添加适当的错误处理代码,如显示错误提示或者使用备用图片。
- 浏览器兼容性:
MutationObserver 兼容性问题
- 浏览器支持情况
- 旧版本浏览器不支持:MutationObserver是一个相对较新的API。在一些旧版本的Internet Explorer(IE)浏览器中完全不支持。例如,IE 11及以下版本没有MutationObserver。这意味着如果你的应用程序需要在这些旧浏览器上运行,就会出现兼容性问题。
- 移动端浏览器差异:虽然大部分现代移动端浏览器支持MutationObserver,但在一些较老的或者特定厂商定制的移动端浏览器版本中,可能会存在部分功能异常或者不完全支持的情况。例如,某些早期的安卓系统自带浏览器或者低版本的iOS Safari浏览器可能对MutationObserver的某些高级特性支持不完善。
- 语法差异与功能限制
- 语法兼容性:不同浏览器在实现MutationObserver的语法细节上可能会有一些细微差异。例如,在某些浏览器中,传递给MutationObserver回调函数的参数对象(包含DOM变化记录的数组)的属性访问方式可能会有不同的性能表现或者限制。在一些严格模式的浏览器环境下,对于未正确声明的变量或者不规范的语法(即使在其他浏览器中可行)可能会抛出错误。
- 观察深度限制:有些浏览器在配置MutationObserver观察DOM树变化时,对于
subtree
选项(用于观察目标节点及其所有后代节点的变化)的深度可能存在限制。例如,在某些情况下,当DOM树非常庞大且嵌套层级很深时,可能无法完整地观察到所有期望的变化,或者会出现观察不及时、遗漏变化记录等问题。
- 与其他技术的兼容性
- 与JavaScript框架的交互:当MutationObserver与流行的JavaScript框架(如React、Vue等)一起使用时,可能会出现兼容性问题。这些框架本身有自己的DOM更新机制和生命周期。例如,在React中,虚拟DOM的更新可能不会直接触发MutationObserver的观察行为,或者可能会与MutationObserver的更新产生冲突,导致重复渲染或者无法正确更新DOM等问题。
- 与CSSOM(CSS对象模型)的交互:虽然MutationObserver主要用于观察DOM变化,但在涉及CSS样式变化导致DOM结构变化(如元素因为
display
属性从none
变为block
而出现,从而引起DOM树的重新布局)时,与CSSOM的交互可能会在某些浏览器中出现兼容性问题。例如,某些浏览器可能无法及时观察到由CSS样式驱动的DOM变化,或者在观察到变化后,回调函数中获取的元素样式相关信息可能不准确。
- 事件冒泡与捕获顺序
- 在复杂的DOM事件处理场景中,MutationObserver的观察行为与事件冒泡和捕获机制可能会产生冲突。例如,当一个元素的添加或删除操作同时触发了事件冒泡和MutationObserver的观察回调时,不同浏览器对于这两者的执行顺序和相互影响可能会有所不同。这可能会导致开发者在编写依赖于事件顺序的代码时出现意外结果,特别是在处理一些具有交互性的DOM元素变化场景(如动态添加可点击元素)时需要特别注意。
IntersectionObserver 应用
- 定义与基本概念
- IntersectionObserver是一个JavaScript API,用于异步观察目标元素与祖先元素或视口(viewport)之间的交叉状态。简单来说,它可以帮助我们知道一个元素什么时候进入或者离开另一个元素(通常是视口)的可见区域。这种观察是高效的,不会导致页面性能的显著下降,因为它利用了浏览器的异步调度机制。
- 工作原理
- 创建观察者对象
- 首先,需要创建一个IntersectionObserver实例。在创建实例时,要传入一个回调函数,这个回调函数会在目标元素和观察区域(可以是视口或者指定的祖先元素)的交叉状态发生变化时被调用。同时,还可以传入一个配置对象来定制观察的条件。
- 例如:
const observer = new IntersectionObserver(callback, config);
- 配置观察条件
- 配置对象(config)可以指定根元素(root),即定义目标元素与之交叉的区域。如果不指定,默认是浏览器的视口。还可以设置根元素的边距(rootMargin),这类似于CSS的外边距,用于扩展或缩小观察区域。另外,阈值(threshold)属性用于定义交叉程度达到多少时触发回调。例如,阈值为0.5表示当目标元素有50%进入观察区域时就触发回调。
- 示例配置:
const config = {root: document.getElementById('scrollContainer'),rootMargin: '0px 0px -200px 0px',threshold: [0, 0.25, 0.5, 0.75, 1] };
- 指定目标元素并开始观察
- 通过
observer.observe(targetElement)
方法来指定要观察的目标元素。一旦开始观察,IntersectionObserver就会在合适的时候(当交叉状态改变时)调用之前传入的回调函数。 - 例如:
const targetElement = document.getElementById('myImage'); observer.observe(targetElement);
- 通过
- 回调函数中的处理
- 回调函数会接收一个数组作为参数,数组中的每个元素是一个IntersectionObserverEntry对象。这个对象包含了目标元素和观察区域交叉状态的详细信息,如交叉比例(intersectionRatio)、目标元素是否在观察区域内(isIntersecting)等。开发者可以根据这些信息来执行相应的操作,比如当目标元素进入视口一定比例后开始加载图片(用于图片懒加载)或者加载更多内容(用于无限滚动)。
- 例如:
function callback(entries) {entries.forEach((entry) => {if (entry.isIntersecting) {// 目标元素进入观察区域,执行加载等操作const target = entry.target;if (target.tagName === 'IMG') {target.src = target.getAttribute('data - src');}}}); }
- 创建观察者对象
- 应用场景
- 图片懒加载
- 这是IntersectionObserver最常见的应用场景之一。网页上有大量图片时,初始加载所有图片会消耗大量带宽和时间。使用IntersectionObserver,当图片进入浏览器视口(或接近视口)时才加载真实图片,有效提高页面的初始加载速度。例如,在电商网站的商品图片展示或者新闻网站的新闻配图场景中非常实用。
- 无限滚动
- 在社交媒体动态、新闻列表等长列表页面,当用户滚动到列表底部接近视口时,IntersectionObserver可以检测到这种状态变化,触发加载更多内容的操作,提供无缝的浏览体验。这避免了用户手动点击“加载更多”按钮的麻烦,增强了用户体验。
- 元素可视性跟踪
- 可以用于跟踪元素是否在可视区域内,比如用于分析用户在页面上实际看到了哪些广告元素,从而更准确地统计广告的曝光率。也可以用于实现一些交互效果,当某个元素完全进入可视区域后才触发动画效果等。
- 图片懒加载
- 性能优势
- 异步执行:IntersectionObserver是异步工作的,它会在浏览器的下一个空闲时间检查交叉状态,不会阻塞主线程。这意味着它不会干扰页面的正常渲染和用户交互,使得页面性能更加流畅。
- 高效计算:浏览器会对交叉状态的计算进行优化,相比于手动通过JavaScript计算元素是否在可视区域(例如,通过获取元素位置和视口位置进行比较),IntersectionObserver的计算效率更高,尤其是在处理多个元素的情况下。
- 局限性和注意事项
- 兼容性问题:虽然IntersectionObserver在现代浏览器中得到了很好的支持,但在一些旧版本浏览器中可能不支持。例如,在较旧的IE浏览器中无法直接使用,需要使用polyfill来实现类似功能。
- 复杂场景的处理:在一些复杂的布局场景中,如CSS变换(transform)、多层嵌套且有不同的定位方式的元素,IntersectionObserver的观察结果可能会受到一定影响。需要仔细考虑观察区域的定义(通过根元素和根元素边距的设置)和元素的布局方式,以确保得到准确的交叉状态观察结果。
MutationObserver和IntersectionObserver的区别
- 观察目标和用途
- MutationObserver
- 主要用于观察DOM树的变化,包括节点的添加、删除、属性修改、文本内容变化等。例如,当一个网页中有动态加载的内容,如通过Ajax添加新的列表项或者修改现有元素的属性(像修改按钮的禁用状态)时,MutationObserver可以很好地捕获这些变化。它更侧重于DOM节点本身的变化情况。
- IntersectionObserver
- 用于观察目标元素与祖先元素或视口(viewport)的交叉状态。通常用于实现懒加载(如图片懒加载、滚动加载更多内容)、无限滚动、元素的可视性检测等功能。比如,在一个包含大量图片的网页中,IntersectionObserver可以检测图片何时进入或离开浏览器的可视区域,从而决定是否加载或卸载图片。
- MutationObserver
- 工作方式和触发机制
- MutationObserver
- 工作方式是异步的。它会将DOM的多个变化记录在一个队列中,然后在一个单独的任务(microtask)中批量处理这些变化,调用回调函数。这意味着即使DOM发生了多个连续的变化,回调函数也可能只被调用一次来处理所有这些变化,避免了频繁触发回调导致的性能问题。
- 触发机制基于DOM树的改变。只要观察的目标元素或者其后代元素发生了符合观察条件(如在配置选项中指定了观察属性变化、子节点变化等)的变化,就会触发回调。
- IntersectionObserver
- 也是异步的,它会在浏览器的下一个空闲时间(例如,在一帧渲染完成之后)检查元素的交叉状态。这使得它能够高效地工作,不会阻塞主线程。
- 触发机制是基于元素的交叉状态。当被观察的目标元素与指定的交叉区域(如视口或另一个元素)的交叉状态发生变化时,例如从完全不可见到部分可见,或者从部分可见变为完全可见,就会触发回调。
- MutationObserver
- 观察参数和配置
- MutationObserver
- 配置相对复杂,需要指定观察的选项,如
attributes
(观察属性变化)、childList
(观察子节点变化)、subtree
(观察目标节点及其所有后代节点的变化)等。还可以通过观察选项的组合来定制观察的具体范围和类型。 - 示例配置如下:
const config = {attributes: true,childList: true,subtree: true };
- 配置相对复杂,需要指定观察的选项,如
- IntersectionObserver
- 配置相对简单,主要包括根元素(用于定义交叉区域,如果不指定则默认是视口)、根元素的边距(用于扩展或缩小交叉区域)、阈值(用于定义交叉程度达到多少时触发回调,例如,0.5表示目标元素50%进入交叉区域时触发)。
- 示例配置如下:
const config = {root: null, // 视口rootMargin: '0px',threshold: 0.5 };
- MutationObserver
- 性能特点
- MutationObserver
- 由于它是对DOM树变化的观察,在DOM结构复杂且变化频繁的场景下,如果观察范围设置得过大(如使用
subtree
观察整个文档树),可能会产生较多的记录和处理工作。但因为其异步处理机制,性能损耗通常可以得到一定程度的控制。
- 由于它是对DOM树变化的观察,在DOM结构复杂且变化频繁的场景下,如果观察范围设置得过大(如使用
- IntersectionObserver
- 性能优势在于其针对性的观察。它只关注元素的交叉状态,对于懒加载等场景,可以精准地在需要的时候触发操作(如加载资源),减少不必要的资源占用。并且由于它也是异步工作的,不会对页面的渲染性能产生明显的干扰。
- MutationObserver
- 应用场景举例
- MutationObserver
- 实时聊天应用:用于观察聊天消息列表(DOM节点)的变化,当有新消息添加(子节点添加)或者消息状态改变(属性修改,如已读/未读状态)时,及时更新界面。
- 动态表单:在一个具有动态添加或删除表单字段的应用中,观察表单元素(DOM树)的变化,确保表单验证等功能始终与表单的实际结构相匹配。
- IntersectionObserver
- 图片懒加载:在网页相册或者电商产品图片展示场景中,当图片进入浏览器可视区域时,使用IntersectionObserver触发图片加载,减少初始页面加载时间和流量消耗。
- 无限滚动列表:在社交媒体动态或者新闻列表等页面,通过IntersectionObserver检测列表底部元素与视口的交叉状态,当接近交叉时,加载更多内容,提供无缝的浏览体验。
- MutationObserver
IntersectionObserver 实例
示例一:图片懒加载
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>图片懒加载示例</title><style>img {width: 300px;height: 200px;/* 使用占位图片 */background-color: gray;background-size: cover;background-position: center;}</style>
</head><body><img src="placeholder.jpg" data-src="image1.jpg" alt="图片1"><img src="placeholder.jpg" data-src="image2.jpg" alt="图片2"><img src="placeholder.jpg" data-src="image3.jpg" alt="图片3"><script>// 创建IntersectionObserver实例,传入回调函数和配置对象const observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {const target = entry.target;// 获取真实图片地址并赋值给src属性触发加载const realSrc = target.getAttribute('data-src');target.src = realSrc;// 停止观察该元素,节省性能(已加载就无需再观察)observer.unobserve(target);}});}, {// 配置阈值,这里设置为元素一进入可视区就触发(阈值为0)threshold: 0});// 获取所有需要懒加载的图片元素并开始观察const images = document.querySelectorAll('img');images.forEach((img) => {observer.observe(img);});</script>
</body></html>
在这个示例中,页面上的图片初始src
为占位图片,通过IntersectionObserver来监测图片是否进入可视区域,当进入时,将data-src
中存储的真实图片地址赋值给src
属性进行加载,加载后停止对该图片的观察。
示例二:无限滚动加载更多内容
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>无限滚动示例</title><style>.item {height: 200px;border: 1px solid gray;margin: 10px;padding: 10px;}</style>
</head><body><div id="content"><div class="item">内容1</div><div class="item">内容2</div><div class="item">内容3</div><!-- 更多内容项 --></div><script>// 创建IntersectionObserver实例const observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {// 模拟加载更多内容,这里简单添加新的内容项const newItems = [];for (let i = 0; i < 3; i++) {const item = document.createElement('div');item.classList.add('item');item.textContent = `新内容 ${i + 1}`;newItems.push(item);}const content = document.getElementById('content');newItems.forEach((newItem) => {content.appendChild(newItem);});}});}, {// 设置根元素为整个文档视口root: null,// 阈值设为元素底部进入可视区一点就触发(0.1)threshold: 0.1});// 获取列表底部的元素(这里假设最后一个元素作为触发加载的标识)const lastItem = document.querySelector('.item:last-child');observer.observe(lastItem);</script>
</body></html>
此示例展示了在一个内容列表中,当滚动到列表底部接近可视区时,通过IntersectionObserver检测到这一变化,然后模拟加载更多的内容项添加到列表中,实现无限滚动的效果。
示例三:元素可视性跟踪及动画触发
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>元素可视性跟踪示例</title><style>.box {width: 100px;height: 100px;background-color: blue;margin: 50px;opacity: 0;transition: opacity 0.5s ease;}</style>
</head><body><div class="box"></div><script>const observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {const target = entry.target;target.style.opacity = 1;}});}, {root: null,threshold: 0.5});const boxElement = document.querySelector('.box');observer.observe(boxElement);</script>
</body></html>
在这个例子中,页面上有一个蓝色方块元素初始是透明的。利用IntersectionObserver来监测它是否进入可视区域,当元素有一半进入可视区域(阈值为0.5)时,改变其opacity
属性为1,触发淡入的动画效果,实现了基于元素可视性来触发动画的功能。