VueUse 学习 —— useResizeObserver

2,464 阅读4分钟

useResizeObserver 是 VueUse 库中的一个自定义 Hook,用于在 Vue 组件中监听元素大小的变化。它基于浏览器提供的 ResizeObserver API,能够观察指定元素或组件的大小变化,并在大小变化时执行相应的回调函数。

使用 useResizeObserver 需要引入 @vueuse/core 库,并确保项目中已经安装了 ResizeObserver API 的 polyfill(如果需要兼容不支持的浏览器)。

用法

<template>
  <div ref="el">
    {{text}}
  </div>
</template>

<script>
import { ref } from 'vue'
import { useResizeObserver } from '@vueuse/core'

export default {
  setup() {
    const el = ref(null)
    const text = ref('')

    useResizeObserver(el, (entries) => {
      const entry = entries[0]
      const { width, height } = entry.contentRect
      text.value = `width: ${width}, height: ${height}`
    })

    return {
      el,
      text,
    }
  }
}
</script>

源码分析

import { tryOnScopeDispose } from '@vueuse/shared'
import { computed, watch } from 'vue-demi'
import type { MaybeComputedElementRef } from '../unrefElement'
import { unrefElement } from '../unrefElement'
import { useSupported } from '../useSupported'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'

export interface ResizeObserverSize {
  readonly inlineSize: number
  readonly blockSize: number
}

export interface ResizeObserverEntry {
  readonly target: Element
  readonly contentRect: DOMRectReadOnly
  readonly borderBoxSize?: ReadonlyArray<ResizeObserverSize>
  readonly contentBoxSize?: ReadonlyArray<ResizeObserverSize>
  readonly devicePixelContentBoxSize?: ReadonlyArray<ResizeObserverSize>
}

export type ResizeObserverCallback = (entries: ReadonlyArray<ResizeObserverEntry>, observer: ResizeObserver) => void

export interface UseResizeObserverOptions extends ConfigurableWindow {
  /**
   * Sets which box model the observer will observe changes to. Possible values
   * are `content-box` (the default), `border-box` and `device-pixel-content-box`.
   *
   * @default 'content-box'
   */
  box?: ResizeObserverBoxOptions
}

declare class ResizeObserver {
  constructor(callback: ResizeObserverCallback)
  disconnect(): void
  observe(target: Element, options?: UseResizeObserverOptions): void
  unobserve(target: Element): void
}

/**
 * Reports changes to the dimensions of an Element's content or the border-box
 *
 * @see https://vueuse.org/useResizeObserver
 * @param target
 * @param callback
 * @param options
 */
export function useResizeObserver(
  target: MaybeComputedElementRef | MaybeComputedElementRef[],
  callback: ResizeObserverCallback,
  options: UseResizeObserverOptions = {},
) {
  const { window = defaultWindow, ...observerOptions } = options
  let observer: ResizeObserver | undefined
  const isSupported = useSupported(() => window && 'ResizeObserver' in window)

  const cleanup = () => {
    if (observer) {
      observer.disconnect()
      observer = undefined
    }
  }

  const targets = computed(() =>
    Array.isArray(target)
      ? target.map(el => unrefElement(el))
      : [unrefElement(target)],
  )

  const stopWatch = watch(
    targets,
    (els) => {
      cleanup()
      if (isSupported.value && window) {
        observer = new ResizeObserver(callback)
        for (const _el of els)
          _el && observer!.observe(_el, observerOptions)
      }
    },
    { immediate: true, flush: 'post', deep: true },
  )

  const stop = () => {
    cleanup()
    stopWatch()
  }

  tryOnScopeDispose(stop)

  return {
    isSupported,
    stop,
  }
}

export type UseResizeObserverReturn = ReturnType<typeof useResizeObserver>

1、定义一些接口类型,用于描述与 ResizeObserver 相关的尺寸和条目信息。ResizeObserverSize 表示一个尺寸对象,包含 inlineSizeblockSize 属性,分别表示内联尺寸和块尺寸。ResizeObserverEntry 表示一个观察者的条目,包含了目标元素、内容矩形的信息以及可选的边框盒子、内容盒子和设备像素内容盒子的尺寸信息。

2、定义 ResizeObserverCallback 类型,表示 ResizeObserver 的回调函数类型。回调函数接收两个参数,第一个参数是一个只读数组,表示所有触发了尺寸变化的条目集合,第二个参数是 ResizeObserver 实例本身。

3、定义UseResizeObserverOptions 接口,用于配置 ResizeObserver 的选项。其中,box 属性用于设置观察的盒模型类型,默认为 'content-box'

4、定义 ResizeObserver 类,它是 ResizeObserver 的封装类,通过构造函数接收一个回调函数作为参数。该类提供了 disconnect() 方法用于断开观察,observe(target, options) 方法用于观察目标元素的尺寸变化,unobserve(target) 方法用于取消观察。

5、定义 useResizeObserver 函数。它接收目标元素的引用或引用数组、回调函数和选项配置作为参数。函数内部首先通过 useSupported 函数判断当前环境是否支持 ResizeObserver,将结果赋值给 isSupported 计算属性。然后,使用 computed 创建 targets 计算属性,将目标元素的引用或引用数组转换为未引用的元素数组。之后,使用 watch 监听 targets 的变化,当目标元素发生变化时,会执行回调函数,创建或断开 ResizeObserver。最后,定义 stop 方法用于停止观察,并在组件销毁时自动调用。

6、定义 UseResizeObserverReturn 类型,表示 useResizeObserver 函数的返回类型。

ResizeObserver

ResizeObserver 是一个用于监测元素尺寸变化的 JavaScript API。它可以观察一个或多个元素的大小变化,并在发生变化时触发回调函数。

在 Web 开发中,当元素的尺寸发生改变时(例如窗口大小调整、元素内容变化或CSS样式变化等),可能需要根据新的尺寸进行一些操作或调整。传统的方法是使用 resize 事件来监听元素尺寸的变化,但该事件只能用于窗口大小的改变,并且在性能上存在一些问题。

ResizeObserver 提供了一种更高效和精确的方式来监测元素尺寸的变化。它能够观察单个元素或多个元素,并在每个元素尺寸变化时立即通知回调函数。通过 ResizeObserver,开发人员可以更方便地实现响应式布局、动态调整元素样式或布局,以及其他基于尺寸变化的交互效果。

使用 ResizeObserver,可以通过创建 ResizeObserver 实例,将要观察尺寸变化的目标元素传递给 observe() 方法,然后在回调函数中处理尺寸变化的逻辑。同时,也可以通过调用 unobserve() 方法来停止对特定元素的观察,或使用 disconnect() 方法断开 ResizeObserver 的所有观察。

需要注意的是,ResizeObserver 是现代浏览器提供的新特性,因此在使用之前需要确保目标浏览器支持该API或者提供了相应的polyfill。

<!DOCTYPE html>
<html>
<head>
  <style>
    .resizable {
      width: 200px;
      height: 200px;
      background-color: #f0f0f0;
      resize: both;
      overflow: auto;
    }
  </style>
</head>
<body>
  <div class="resizable">Resizable Element</div>

  <script>
    const element = document.querySelector('.resizable');

    // 创建 ResizeObserver 实例
    const resizeObserver = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        console.log(`元素的新尺寸:宽度 ${width}px,高度 ${height}px`);
        
        // 在这里可以根据新的尺寸进行相应的操作
        // 例如调整布局、更新样式等
      }
    });

    // 开始观察目标元素
    resizeObserver.observe(element);
  </script>
</body>
</html>

在上面的例子中,我们创建了一个可调整大小的 div 元素,并给它添加了一个类名 .resizable。然后,我们使用 JavaScript 创建了一个 ResizeObserver 实例,并通过 observe() 方法将目标元素传递给它进行观察。

当我们通过拖动 .resizable 元素的边框来改变它的尺寸时,ResizeObserver 的回调函数会被触发,并将包含了尺寸信息的 entries 数组传递给回调函数。