Vue3组合式函数(监听DOM尺寸 useResizeObserver)

简介: 本文介绍如何使用 `ResizeObserver` API 编写 Vue3 的通用组合式函数 `useResizeObserver`,该函数可方便地观察一个或多个元素的尺寸变化,并执行指定回调。`ResizeObserver` 接口用于监控 `Element` 或 `SVGElement` 的尺寸变化,避免了无限回调循环问题。

使用 ResizeObserver API 编写 Vue3 通用组合式函数

监听DOM尺寸 useResizeObserver 在线功能预览

/**
 * 组合式函数
 * 使用 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
  }
}

ResizeObserver

ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。

内容盒是盒模型放置内容的部分,这意味着边框盒减去内边距和边框的宽度就是内容盒边框盒包含内容、内边距和边框。有关进一步阐述,参见盒模型。

ResizeObserver 避免了通过回调函数调整大小时,通常创建的无限回调循环和循环依赖项。它只能通过在后续的帧中处理 DOM 中更深层次的元素来做到这一点。如果它的实现遵循规范,则应在绘制前和布局后调用 resize 事件。

构造函数 ResizeObserver()

ResizeObserver 构造函数创建一个新的 ResizeObserver 对象,它可以用于监听 Element 内容盒或边框盒或者 SVGElement 边界尺寸的大小。

  • 语法

    new ResizeObserver(callback)
    
  • 参数

    • callback: 每当观测的元素调整大小时,调用该函数。该函数接收两个参数:

      • entries: 一个 ResizeObserverEntry 对象数组,可以用于获取每个元素改变后的新尺寸

        ResizeObserverEntry: ResizeObserverEntry 接口是传递给 ResizeObserver() 构造函数中的回调函数参数的对象,它允许你获取真正在观察的 ElementSVGElement 最新的大小。

        • 属性:

          • ResizeObserverEntry.borderBoxSize 只读
            一个对象,当运行回调时,该对象包含着正在观察元素的新边框盒的大小。

          • ResizeObserverEntry.contentBoxSize 只读
            一个对象,当运行回调时,该对象包含着正在观察元素的新内容盒的大小。

          • ResizeObserverEntry.devicePixelContentBoxSize 只读
            一个对象,当运行回调时,该对象包含着正在观察元素的新内容盒的大小(以设备像素为单位)。

          • ResizeObserverEntry.contentRect 只读
            一个对象,当运行回调时,该对象包含着正在观察元素新大小的 DOMRectReadOnly 对象。请注意,这比以上两个属性有着更好的支持,但是它是 Resize Observer API 早期实现遗留下来的,出于对浏览器的兼容性原因,仍然被保留在规范中,并且在未来的版本中可能被弃用。

          • ResizeObserverEntry.target 只读
            对正在观察 ElementSVGElement 的引用。

            ::: 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() 方法取消所有的对 ElementSVGElement 目标的监听。

    • 语法:

      disconnect()
      
  • ResizeObserver.observe(): 开始对指定 Element 的监听。

    ResizeObserver 接口的 observe() 方法用于监听指定的 ElementSVGElement

    • 语法:

      observe(target)
      observe(target, options)
      
    • 参数:

      • target: 要监听的 ElementSVGElement 的引用。
      • options: 一个可选的对象,允许你为监听的对象设置参数。目前,这只有一个参数:
        • box: 设置 observer 将监听的盒模型。可能的值是:
          • content-box(默认): CSS 中定义的内容区域的大小。
          • border-box: CSS 中定义的边框区域的大小。
          • device-pixel-content-box: 在对元素或其祖先应用任何 CSS 转换之前,CSS 中定义的内容区域的大小,以设备像素为单位。
  • ResizeObserver.unobserve(): 结束对指定 Element 的监听。

    ResizeObserver 接口的 unobserve() 方法结束对指定的 ElementSVGElement 的监听。

    • 语法:

      unobserve(target)
      
    • 参数:

      • target: 对不要监听的 ElementSVGElement 的引用。

示例

我们使用 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)
  }
})
相关文章
|
4月前
|
JavaScript vr&ar
vue3通过ref获取dom元素并修改样式
vue3通过ref获取dom元素并修改样式
422 0
|
4月前
|
JavaScript
vue监听dom元素的宽高变化和自定义指令监听dom元素的宽高变化
vue监听dom元素的宽高变化和自定义指令监听dom元素的宽高变化
408 0
|
4月前
|
存储 JavaScript 数据可视化
vue3+echarts应用——深度遍历html的dom结构并用树图进行可视化
vue3+echarts应用——深度遍历html的dom结构并用树图进行可视化
132 1
|
存储 JavaScript 前端开发
JavaScript函数和BOM及DOM编程(详细总结-无尿点)(一)
JavaScript函数和BOM及DOM编程(详细总结-无尿点)(一)
59 0
|
XML JavaScript 前端开发
JavaScript函数和BOM及DOM编程(详细总结-无尿点)(二)
JavaScript函数和BOM及DOM编程(详细总结-无尿点)(二)
37 0
|
3月前
|
前端开发 JavaScript 程序员
探索JavaScript宝库:打开基础知识与实用技能之门(数据类型与变量+ 条件与循环+函数与模块+DOM+异常+ES6)
探索JavaScript宝库:打开基础知识与实用技能之门(数据类型与变量+ 条件与循环+函数与模块+DOM+异常+ES6)
28 0
|
4月前
|
XML 存储 JavaScript
XML DOM 加载函数
`loadXMLString()`函数被封装在名为&quot;loadxmlstring.js&quot;的外部JavaScript文件中,用于加载XML文档。在示例HTML中,这个函数被调用来处理动态生成的XML字符串,该字符串描述了一本名为&quot;Everyday Italian&quot;的书籍信息。之后,可以在`code goes here.....`处进一步处理`xmlDoc`对象。
|
4月前
|
XML 存储 JavaScript
XML DOM 加载函数
`loadXMLDoc()` 是一个JavaScript函数,用于加载XML文档。它支持XMLHttpRequest和ActiveXObject,适应不同浏览器。函数定义存储在名为`loadxmldoc.js`的外部文件中,包含在HTML `&lt;head&gt;`标签内,然后在页面脚本中调用来获取XML数据,如`books.xml`。接下来的章节将介绍如何处理加载的数据。
|
4月前
|
JavaScript 前端开发
在 Vue 中,如何使用`ref`和`$refs`来监听DOM元素的变化?
在 Vue 中,如何使用`ref`和`$refs`来监听DOM元素的变化?
531 0
|
4月前
|
JavaScript 前端开发
第15节:Vue3 DOM 更新完成nextTick()
第15节:Vue3 DOM 更新完成nextTick()
87 0