使用 ResizeObserver API 编写 Vue3 通用组合式函数
/**
* 组合式函数
* 使用 ResizeObserver 观察 DOM 元素尺寸变化
*
* 该函数提供了一种方便的方式来观察一个或多个元素的尺寸变化,并在变化时执行指定的回调函数。
*
* @param target 要观察的目标,可以是 Ref 对象、Ref 数组、HTMLElement 或 HTMLElement 数组
* @param callback 当元素尺寸变化时调用的回调函数
* @param options ResizeObserver 选项,用于定制观察行为
* @returns 返回一个对象,包含停止和开始观察的方法,使用者可以调用 start 方法开始观察,调用 stop 方法停止观察
*/
import { ref, toValue, computed, watch, onBeforeUnmount } from 'vue'
import type { Ref } from 'vue'
export function useResizeObserver(target: Ref | Ref[] | HTMLElement | HTMLElement[], callback: ResizeObserverCallback, options = {} ) {
let observer: ResizeObserver | undefined
const stopObservation = ref(false)
const targets = computed(() => {
const targetsValue = toValue(target)
if (targetsValue) {
if (Array.isArray(targetsValue)) {
return targetsValue.map((el: any) => toValue(el)).filter((el: any) => el)
} else {
return [targetsValue]
}
}
return []
})
// 定义清理函数,用于断开 ResizeObserver 的连接
const cleanup = () => {
if (observer) {
observer.disconnect()
observer = undefined
}
}
// 初始化 ResizeObserver,开始观察目标元素
const observeElements = () => {
if (targets.value.length && !stopObservation.value) {
observer = new ResizeObserver(callback)
targets.value.forEach((element: HTMLElement) => observer!.observe(element, options))
}
}
// 监听 targets 的变化,当 targets 变化时,重新建立 ResizeObserver 观察
watch(
() => targets.value,
() => {
cleanup()
observeElements()
},
{
immediate: true, // 立即触发回调,以便初始状态也被观察
flush: 'post'
}
)
const stop = () => {
stopObservation.value = true
cleanup()
}
const start = () => {
stopObservation.value = false
observeElements()
}
// 在组件卸载前清理 ResizeObserver
onBeforeUnmount(() => cleanup())
return {
stop,
start
}
}
监听DOM尺寸 useResizeObserver 在线功能预览
ResizeObserver 文档
ResizeObserver
接口监视 Element
内容盒或边框盒或者 SVGElement
边界尺寸的变化。
内容盒是盒模型放置内容的部分,这意味着边框盒减去内边距和边框的宽度就是内容盒。边框盒包含内容、内边距和边框。有关进一步阐述,参见盒模型。
ResizeObserver
避免了通过回调函数调整大小时,通常创建的无限回调循环和循环依赖项。它只能通过在后续的帧中处理 DOM
中更深层次的元素来做到这一点。如果它的实现遵循规范,则应在绘制前和布局后调用 resize
事件。
构造函数 ResizeObserver()
ResizeObserver
构造函数创建一个新的 ResizeObserver
对象,它可以用于监听 Element
内容盒或边框盒或者 SVGElement
边界尺寸的大小。
-
语法
new ResizeObserver(callback)
-
参数
callback
: 每当观测的元素调整大小时,调用该函数。该函数接收两个参数:entries
: 一个ResizeObserverEntry
对象数组,可以用于获取每个元素改变后的新尺寸。ResizeObserverEntry
:ResizeObserverEntry
接口是传递给ResizeObserver()
构造函数中的回调函数参数的对象,它允许你获取真正在观察的Element
或SVGElement
最新的大小。-
属性:
-
ResizeObserverEntry.borderBoxSize
只读
一个对象,当运行回调时,该对象包含着正在观察元素的新边框盒的大小。 -
ResizeObserverEntry.contentBoxSize
只读
一个对象,当运行回调时,该对象包含着正在观察元素的新内容盒的大小。 -
ResizeObserverEntry.devicePixelContentBoxSize
只读
一个对象,当运行回调时,该对象包含着正在观察元素的新内容盒的大小(以设备像素为单位)。 -
ResizeObserverEntry.contentRect
只读
一个对象,当运行回调时,该对象包含着正在观察元素新大小的DOMRectReadOnly
对象。请注意,这比以上两个属性有着更好的支持,但是它是Resize Observer API
早期实现遗留下来的,出于对浏览器的兼容性原因,仍然被保留在规范中,并且在未来的版本中可能被弃用。 -
ResizeObserverEntry.target
只读
对正在观察Element
或SVGElement
的引用。
::: tip 备注
内容盒是放置内容的盒子,即边框盒减去内边距和边框宽度。边框盒包含内容、内边距和边框。更多解释参见盒模型。
::: -
-
observer
: 对ResizeObserver
自身的引用,因此需要它的时候,你要从回调函数的内部访问。例如,这可用于在达到特定的情况时,自动取消对观察者的监听,但如果你不需要它,可以省略它。
function callback(entries, observer) { for (const entry of entries) { // Do something to each entry // and possibly something to the observer itself } }
-
示例
const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { if (entry.contentBoxSize) { if (entry.contentBoxSize[0]) { h1Elem.style.fontSize = `${Math.max( 1.5, entry.contentBoxSize[0].inlineSize / 200, )}rem` pElem.style.fontSize = `${Math.max( 1, entry.contentBoxSize[0].inlineSize / 600, )}rem` } else { // legacy path h1Elem.style.fontSize = `${Math.max( 1.5, entry.contentBoxSize.inlineSize / 200, )}rem` pElem.style.fontSize = `${Math.max( 1, entry.contentBoxSize.inlineSize / 600, )}rem` } } else { h1Elem.style.fontSize = `${Math.max( 1.5, entry.contentRect.width / 200, )}rem` pElem.style.fontSize = `${Math.max(1, entry.contentRect.width / 600)}rem` } } console.log('Size changed') }) resizeObserver.observe(divElem)
方法
-
ResizeObserver.disconnect()
: 取消特定观察者目标上所有对Element
的监听。ResizeObserver
接口的disconnect()
方法取消所有的对Element
或SVGElement
目标的监听。-
语法:
disconnect()
-
-
ResizeObserver.observe()
: 开始对指定Element
的监听。ResizeObserver
接口的observe()
方法用于监听指定的Element
或SVGElement
。-
语法:
observe(target) observe(target, options)
-
参数:
target
: 要监听的Element
或SVGElement
的引用。options
: 一个可选的对象,允许你为监听的对象设置参数。目前,这只有一个参数:box
: 设置observer
将监听的盒模型。可能的值是:content-box
(默认):CSS
中定义的内容区域的大小。border-box
:CSS
中定义的边框区域的大小。device-pixel-content-box
: 在对元素或其祖先应用任何CSS
转换之前,CSS
中定义的内容区域的大小,以设备像素为单位。
-
-
ResizeObserver.unobserve()
: 结束对指定Element
的监听。ResizeObserver
接口的unobserve()
方法结束对指定的Element
或SVGElement
的监听。-
语法:
unobserve(target)
-
参数:
target
: 对不要监听的Element
或SVGElement
的引用。
-
示例
我们使用 resize observer
去更改头和段落的 font-size
,随着 slider
的值被改变,也引起了包含的 <div>
的宽度改变。这展示了你可以响应元素大小的变化,即使它们与视口无关。
我们也提供了一个 checkbox
来关闭和打开 observer
。如果它是关闭的,文本将不会随着 <div>
的宽度改变而改变。
const h1Elem = document.querySelector("h1")
const pElem = document.querySelector("p")
const divElem = document.querySelector("body > div")
const slider = document.querySelector('input[type="range"]')
const checkbox = document.querySelector('input[type="checkbox"]')
divElem.style.width = "600px"
slider.addEventListener("input", () => {
divElem.style.width = `${slider.value}px`
})
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (entry.contentBoxSize) {
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
const contentBoxSize = Array.isArray(entry.contentBoxSize)
? entry.contentBoxSize[0]
: entry.contentBoxSize
h1Elem.style.fontSize = `${Math.max(
1.5,
contentBoxSize.inlineSize / 200,
)}rem`
pElem.style.fontSize = `${Math.max(
1,
contentBoxSize.inlineSize / 600,
)}rem`
} else {
h1Elem.style.fontSize = `${Math.max(
1.5,
entry.contentRect.width / 200,
)}rem`
pElem.style.fontSize = `${Math.max(1, entry.contentRect.width / 600)}rem`
}
}
console.log("Size changed")
})
resizeObserver.observe(divElem)
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
resizeObserver.observe(divElem)
} else {
resizeObserver.unobserve(divElem)
}
})