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

使用 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() 构造函数中的回调函数参数的对象,它允许你获取真正在观察的 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)
  }
})
Vue 3的组合式API是一种新的编程模式,它使得在Vue组件中可以更灵活地组织和复用逻辑。下面是对Vue 3组合式API的介绍: 1. Composition API(组合式API):Vue 3中引入了Composition API,它允许我们将逻辑按照功能进行组合,而不是按照生命周期钩子进行划分。这样可以更好地组织和复用代码。 2. setup函数:在Vue 3中,我们需要在组件中使用setup函数来定义组合式API。setup函数在组件创建之前执行,并且接收两个参数:props和context。我们可以在setup函数中定义响应式数据、计算属性、方法等。 3. reactive函数:reactive函数Vue 3中用来创建响应式数据的函数。我们可以使用reactive函数将普通对象转换为响应式对象,从而实现数据的双向绑定。 4. ref函数:ref函数Vue 3中用来创建单个响应式数据的函数。与reactive函数不同,ref函数返回一个包装过的对象,我们需要通过.value属性来访问和修改数据。 5. computed函数:computed函数用来创建计算属性。与Vue 2中的计算属性类似,我们可以使用computed函数来定义一个依赖其他响应式数据的属性。 6. watch函数:watch函数用来监听响应式数据的变化。我们可以使用watch函数来执行一些副作用操作,比如发送网络请求或者更新DOM。 7. 生命周期钩子:在Vue 3中,生命周期钩子函数被废弃了,取而代之的是使用setup函数来处理组件的生命周期逻辑。我们可以在setup函数中使用onMounted、onUpdated等函数来模拟Vue 2中的生命周期钩子。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值