Skip to content

监听DOM尺寸 useResizeObserver

使用 ResizeObserver 观察 DOM 元素尺寸变化的组合式函数

Show Source Code
ts
/**
 * 组合式函数
 * 使用 ResizeObserver 观察 DOM 元素尺寸变化
 *
 * 该函数提供了一种方便的方式来观察一个或多个元素的尺寸变化,并在变化时执行指定的回调函数
 *
 * @param target 要观察的目标,可以是 Ref 对象、Ref 数组、HTMLElement 或 HTMLElement 数组
 * @param callback 当元素尺寸变化时调用的回调函数
 * @param options ResizeObserver 选项,用于定制观察行为
 * @returns 返回一个对象,包含停止和开始观察的方法,使用者可以调用 start 方法开始观察,调用 stop 方法停止观察
 */
import { ref, toValue, computed, watch, onBeforeUnmount, onMounted, getCurrentInstance } from 'vue'
import type { Ref } from 'vue'
export function useResizeObserver(
  target: Ref | Ref[] | HTMLElement | HTMLElement[],
  callback: ResizeObserverCallback,
  options: object = {}
) {
  const isSupported = useSupported(() => window && 'ResizeObserver' in window)
  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 (isSupported.value && 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
  }
}
// 辅助函数
export function useSupported(callback: () => unknown) {
  const isMounted = useMounted()
  return computed(() => {
    // to trigger the ref
    isMounted.value
    return Boolean(callback())
  })
}
export function useMounted() {
  const isMounted = ref(false)
  // 获取当前组件的实例
  const instance = getCurrentInstance()
  if (instance) {
    onMounted(() => {
      isMounted.value = true
    }, instance)
  }
  return isMounted
}

参考文档

基本使用

请缩放下面的盒子来观察变化


borderBlockSize:

borderInlineSize:

contentBlockSize:

contentInlineSize:

contentRect:

x:

y:

width:

height:

top:

bottom:

right:

left:

devicePixelContentBlockSize:

devicePixelContentInlineSize:

vue
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useResizeObserver } from 'vue-amazing-ui'

const el = ref(null)
const state = reactive({
  borderBlockSize: null,
  borderInlineSize: null,
  contentBlockSize: null,
  contentInlineSize: null,
  x: null,
  y: null,
  width: null,
  height: null,
  top: null,
  bottom: null,
  right: null,
  left: null,
  devicePixelContentBlockSize: null,
  devicePixelContentInlineSize: null
})
useResizeObserver(el, (entries: ResizeObserverEntry[], observer: ResizeObserver) => {
  console.log('entries', entries)
  console.log('observer', observer)
  const entry = entries[0]
  state.borderBlockSize = entry.borderBoxSize[0].blockSize
  state.borderInlineSize = entry.borderBoxSize[0].inlineSize
  state.contentBlockSize = entry.contentBoxSize[0].blockSize
  state.contentInlineSize = entry.contentBoxSize[0].inlineSize
  state.x = entry.contentRect.x
  state.y = entry.contentRect.y
  state.width = entry.contentRect.width
  state.height = entry.contentRect.height
  state.top = entry.contentRect.top
  state.bottom = entry.contentRect.bottom
  state.right = entry.contentRect.right
  state.left = entry.contentRect.left
  state.devicePixelContentBlockSize = entry.devicePixelContentBoxSize[0].blockSize
  state.devicePixelContentInlineSize = entry.devicePixelContentBoxSize[0].inlineSize
})
</script>
<template>
  <h3>Resize the box to see changes</h3>
  <div class="m-size-wrap">
    <textarea ref="el" class="resizer" disabled />
    <div class="m-size">
      <p>borderBlockSize: {{ state.borderBlockSize }}</p>
      <p>borderInlineSize: {{ state.borderInlineSize }}</p>
      <p>contentBlockSize: {{ state.contentBlockSize }}</p>
      <p>contentInlineSize: {{ state.contentInlineSize }}</p>
      <h3>contentRect:</h3>
      <p>x: {{ state.x }}</p>
      <p>y: {{ state.y }}</p>
      <p>width: {{ state.width }}</p>
      <p>height: {{ state.height }}</p>
      <p>top: {{ state.top }}</p>
      <p>bottom: {{ state.bottom }}</p>
      <p>right: {{ state.right }}</p>
      <p>left: {{ state.left }}</p>
      <p>devicePixelContentBlockSize: {{ state.devicePixelContentBlockSize }}</p>
      <p>devicePixelContentInlineSize: {{ state.devicePixelContentInlineSize }}</p>
    </div>
  </div>
</template>
<style lang="less" scoped>
.m-size-wrap {
  position: relative;
  .resizer {
    background: #222;
    color: #fff;
    resize: both;
    padding: 16px;
    min-width: 300px;
    min-height: 450px;
    max-width: 688px;
    border: 1px solid #2e2e32;;
    border-radius: 4px;
    outline: none;
    white-space: pre;
    overflow-wrap: normal;
    overflow: hidden;
    display: block;
    font-size: 16px;
    box-shadow: #2e2e32 0 0 0 1px;
    margin: 8px 0;
    background: #1b1b1f;
    touch-action: manipulation;
  }
  .m-size {
    top: 12px;
    left: 16px;
    position: absolute;
    color: #fff;
    font-size: 16px;
    h3 {
      margin-top: 0;
    }
  }
}
</style>

Params

参数说明类型默认值
target要观察的目标,可以是 Ref 对象、Ref 数组、HTMLElementHTMLElement 数组Ref | Ref[] | HTMLElement | HTMLElement[]undefined
callback当元素尺寸变化时调用的回调函数ResizeObserverCallbackundefined
optionsResizeObserver 选项,用于定制观察行为,参考文档object{}

Released under the MIT License.