各位观众老爷,早上好/中午好/晚上好!欢迎来到今天的 “JS ResizeObserver:监听元素内容区域尺寸变化与响应式布局” 讲座。今天咱们就来聊聊这个在前端开发中相当实用,但又经常被忽略的小工具——ResizeObserver
。
开场白:为什么需要 ResizeObserver?
想象一下,你在开发一个复杂的Web应用,页面布局需要根据不同设备的屏幕尺寸,甚至元素的自身尺寸进行动态调整。你可能会用到window.addEventListener('resize', ...)
来监听窗口的resize事件,但这种方式有几个问题:
- 全局监听,性能损耗: 每次窗口resize,都会触发回调函数,即便你只想监听某个特定元素的尺寸变化。这无疑是对性能的浪费。
- 元素尺寸变化检测不精准: 有些元素的尺寸变化并非由窗口resize引起,比如CSS动画、JavaScript动态修改、子元素内容撑开等等。
window.resize
监听不到这些变化。 - 回调函数执行频率过高: 窗口resize事件触发非常频繁,导致回调函数频繁执行,可能会引发性能问题,甚至卡顿。
这时候,ResizeObserver
就派上用场了。它能够监听特定元素的内容区域尺寸变化,并在变化发生时执行回调函数,而且还针对性能进行了优化。简单来说,它更精准、更高效。
ResizeObserver 是什么?
ResizeObserver
是一个现代 JavaScript API,它允许你监听 HTML 元素的尺寸变化。与监听全局的 window.resize
事件不同,ResizeObserver
专注于监听特定元素。更重要的是,它监听的是元素的内容区域(content box)的尺寸变化,这意味着它能更准确地捕捉到由各种因素引起的尺寸变化。
语法和基本用法
ResizeObserver
的基本使用流程如下:
-
创建 ResizeObserver 实例: 使用
new ResizeObserver(callback)
创建一个 ResizeObserver 对象,并传入一个回调函数。这个回调函数会在被监听元素的尺寸发生变化时被调用。 -
定义回调函数: 回调函数接收一个
entries
参数,它是一个ResizeObserverEntry
对象的数组。每个ResizeObserverEntry
对象都包含了关于被监听元素尺寸变化的信息。 -
监听元素: 使用
observe(element)
方法开始监听指定的 HTML 元素。 -
停止监听: 当不再需要监听某个元素时,可以使用
unobserve(element)
方法停止监听。可以使用disconnect()
停止所有监听。
代码示例:最简单的监听
const element = document.getElementById('myElement');
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
console.log(`元素尺寸变化:宽度 = ${width}px, 高度 = ${height}px`);
// 在这里执行你的响应式布局逻辑
});
});
observer.observe(element);
// 在不需要监听时,停止监听
// observer.unobserve(element);
// observer.disconnect();
在这个例子中,我们创建了一个 ResizeObserver
实例,并传入一个回调函数。回调函数会遍历 entries
数组,并从每个 ResizeObserverEntry
对象中提取出 contentRect
属性,该属性包含了元素的宽度和高度。然后,我们就可以在回调函数中执行我们的响应式布局逻辑。
ResizeObserverEntry 对象详解
ResizeObserverEntry
对象包含了关于被监听元素尺寸变化的详细信息。它主要包含以下属性:
属性 | 类型 | 描述 |
---|---|---|
target |
Element | 被监听的 HTML 元素。 |
contentRect |
DOMRectReadOnly | 一个只读的矩形对象,包含了元素的内容区域的尺寸信息(宽度、高度、top、left、bottom、right)。注意:这里的尺寸不包括padding、border和margin。 |
borderBoxSize |
ResizeObserverSize[] | 一个数组,包含了元素的边框盒(border box)的尺寸信息。在大多数情况下,这个数组只有一个元素。 |
contentBoxSize |
ResizeObserverSize[] | 一个数组,包含了元素的内容盒(content box)的尺寸信息。在大多数情况下,这个数组只有一个元素。 |
devicePixelContentBoxSize |
ResizeObserverSize[] | 一个数组,包含了元素的内容盒(content box)的尺寸信息,以设备像素为单位。在大多数情况下,这个数组只有一个元素。 |
注意: borderBoxSize
、contentBoxSize
和 devicePixelContentBoxSize
属性返回的是一个数组,这是为了支持未来的扩展,例如支持多列布局。但在大多数情况下,这个数组只有一个元素。
代码示例:使用 contentRect 和 borderBoxSize
const element = document.getElementById('myElement');
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width: contentWidth, height: contentHeight } = entry.contentRect;
const borderBoxSize = entry.borderBoxSize[0];
const { inlineSize: borderWidth, blockSize: borderHeight } = borderBoxSize;
console.log(`内容区域:宽度 = ${contentWidth}px, 高度 = ${contentHeight}px`);
console.log(`边框盒子:宽度 = ${borderWidth}px, 高度 = ${borderHeight}px`);
// 在这里执行你的响应式布局逻辑
});
});
observer.observe(element);
在这个例子中,我们同时使用了 contentRect
和 borderBoxSize
属性来获取元素的尺寸信息。contentRect
包含了内容区域的尺寸,而 borderBoxSize
包含了边框盒的尺寸。你可以根据实际需求选择合适的属性。
ResizeObserver 的性能优化
ResizeObserver
在性能方面做了很多优化,以避免频繁的回调函数执行带来的性能问题。
-
批量处理:
ResizeObserver
会将多个尺寸变化合并成一个回调函数执行。这意味着,如果在短时间内发生了多次尺寸变化,ResizeObserver
只会执行一次回调函数,并将所有的变化信息传递给回调函数。 -
异步执行:
ResizeObserver
的回调函数是异步执行的。这意味着,回调函数不会阻塞主线程,从而避免了页面卡顿。 -
避免死循环:
ResizeObserver
会自动避免由于回调函数中的代码引起的死循环。如果你在回调函数中修改了元素的尺寸,ResizeObserver
会自动停止监听,以避免无限循环。
代码示例:利用 requestAnimationFrame 优化回调函数
为了进一步优化性能,我们可以结合 requestAnimationFrame
来使用 ResizeObserver
。requestAnimationFrame
可以在浏览器下一次重绘之前执行回调函数,这可以确保我们的布局逻辑在页面渲染之前执行,从而避免页面闪烁。
const element = document.getElementById('myElement');
const observer = new ResizeObserver(entries => {
requestAnimationFrame(() => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
console.log(`元素尺寸变化:宽度 = ${width}px, 高度 = ${height}px`);
// 在这里执行你的响应式布局逻辑
});
});
});
observer.observe(element);
在这个例子中,我们将回调函数放在 requestAnimationFrame
的回调函数中执行。这样可以确保我们的布局逻辑在页面渲染之前执行,从而提高页面的性能。
ResizeObserver 的应用场景
ResizeObserver
在响应式布局中有很多应用场景,例如:
-
自适应容器: 根据容器的尺寸动态调整内部元素的布局。例如,当容器的宽度小于某个阈值时,将内部元素垂直排列;当容器的宽度大于某个阈值时,将内部元素水平排列。
-
文本截断: 根据容器的尺寸动态截断文本,以避免文本溢出。
-
图片缩放: 根据容器的尺寸动态缩放图片,以确保图片始终适应容器的大小。
-
第三方组件集成: 当集成第三方组件时,可以使用
ResizeObserver
监听组件的尺寸变化,并根据变化调整页面布局。
代码示例:自适应容器
<div id="container">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
</div>
<style>
#container {
display: flex;
flex-wrap: wrap; /* 默认水平排列,允许换行 */
}
.item {
width: 200px;
height: 100px;
background-color: #eee;
margin: 10px;
}
</style>
<script>
const container = document.getElementById('container');
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width } = entry.contentRect;
if (width < 600) {
container.style.flexDirection = 'column'; // 宽度小于 600px 时,垂直排列
} else {
container.style.flexDirection = 'row'; // 宽度大于等于 600px 时,水平排列
}
});
});
observer.observe(container);
</script>
在这个例子中,我们创建了一个容器,并在容器中放置了三个子元素。我们使用 ResizeObserver
监听容器的尺寸变化。当容器的宽度小于 600px 时,我们将容器的 flex-direction
属性设置为 column
,使子元素垂直排列;当容器的宽度大于等于 600px 时,我们将容器的 flex-direction
属性设置为 row
,使子元素水平排列。
代码示例:文本截断
<div id="text-container">
<p id="text">这是一段很长的文本,需要根据容器的宽度进行截断。</p>
</div>
<style>
#text-container {
width: 300px;
border: 1px solid #ccc;
overflow: hidden; /* 隐藏溢出文本 */
}
#text {
white-space: nowrap; /* 禁止换行 */
}
</style>
<script>
const textContainer = document.getElementById('text-container');
const text = document.getElementById('text');
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width } = entry.contentRect;
if (text.scrollWidth > width) {
// 文本宽度大于容器宽度,进行截断
text.style.textOverflow = 'ellipsis'; // 使用省略号截断
text.style.overflow = 'hidden';
} else {
// 文本宽度小于等于容器宽度,不进行截断
text.style.textOverflow = 'clip'; // 取消省略号
text.style.overflow = 'visible';
}
});
});
observer.observe(textContainer);
</script>
在这个例子中,我们创建了一个容器,并在容器中放置了一段文本。我们使用 ResizeObserver
监听容器的尺寸变化。当文本的宽度大于容器的宽度时,我们使用 text-overflow: ellipsis
属性截断文本,并使用省略号表示被截断的文本;当文本的宽度小于等于容器的宽度时,我们取消文本的截断。
ResizeObserver 的兼容性
ResizeObserver
的兼容性还是不错的,主流浏览器都支持。
浏览器 | 版本 | 支持情况 |
---|---|---|
Chrome | 64+ | 支持 |
Firefox | 69+ | 支持 |
Safari | 13+ | 支持 |
Edge | 79+ | 支持 |
Opera | 51+ | 支持 |
iOS Safari | 13+ | 支持 |
Android Chrome | 64+ | 支持 |
对于不支持 ResizeObserver
的浏览器,可以使用 polyfill 来提供兼容性。比如可以使用这个polyfill: https://github.com/que-etc/resize-observer-polyfill
总结
ResizeObserver
是一个非常有用的 API,它可以帮助我们更精准、更高效地监听元素的尺寸变化,从而实现更灵活、更强大的响应式布局。在开发复杂的Web应用时,不妨考虑使用 ResizeObserver
来提升用户体验。
总结表格
特性 | ResizeObserver | window.resize |
---|---|---|
监听对象 | 单个 HTML 元素的内容区域尺寸变化 | 整个浏览器窗口 |
触发时机 | 元素内容区域尺寸实际发生变化时 | 浏览器窗口尺寸发生变化时 |
性能 | 更精准、更高效,避免不必要的回调执行 | 全局监听,性能损耗较大 |
适用场景 | 需要监听特定元素尺寸变化的场景 | 需要监听整个浏览器窗口尺寸变化的场景 |
是否异步 | 异步执行 | 同步执行 |
好了,今天的讲座就到这里。希望大家对 ResizeObserver
有了更深入的了解。有什么问题,欢迎随时提问。祝大家编程愉快!
下次再见!