手写VueUse的useElementSize函数实现

以下是一个手写实现的 useElementSize 函数,用于监听元素尺寸变化并返回响应式尺寸数据:

typescript 复制代码
import {
  ref,
  watch,
  onUnmounted,
  unref,
  type Ref,
  type ComponentPublicInstance
} from 'vue'

interface UseElementSizeOptions {
  initialSize?: { width?: number; height?: number }
  box?: 'content-box' | 'border-box'
}

export function useElementSize(
  target: Ref<HTMLElement | ComponentPublicInstance | null>,
  options: UseElementSizeOptions = {}
) {
  const { initialSize = {}, box = 'content-box' } = options
  const width = ref(initialSize.width ?? 0)
  const height = ref(initialSize.height ?? 0)
  let observer: ResizeObserver | null = null

  const getElement = (): HTMLElement | null => {
    const el = unref(target)
    return (el as ComponentPublicInstance)?.$el ?? el
  }

  const updateSize = (entries: ResizeObserverEntry[]) => {
    const entry = entries[0]
    if (!entry) return

    const boxSize = entry[box === 'border-box' ? 'borderBoxSize' : 'contentBoxSize']
    const hasBorderBoxSize = box === 'border-box' && boxSize
    
    if (hasBorderBoxSize) {
      width.value = boxSize[0].inlineSize
      height.value = boxSize[0].blockSize
    } else {
      width.value = entry.contentRect.width
      height.value = entry.contentRect.height
    }
  }

  const observeElement = (el: HTMLElement | null) => {
    if (!el) return
    
    observer = new ResizeObserver(updateSize)
    observer.observe(el, { box: box === 'border-box' ? 'border-box' : 'content-box' })
  }

  const disconnectObserver = () => {
    if (observer) {
      observer.disconnect()
      observer = null
    }
  }

  watch(
    () => getElement(),
    (newEl, oldEl, onCleanup) => {
      disconnectObserver()
      observeElement(newEl)
      
      onCleanup(() => {
        disconnectObserver()
      })
    },
    { immediate: true }
  )

  const stop = () => {
    disconnectObserver()
  }

  onUnmounted(() => {
    disconnectObserver()
  })

  return {
    width,
    height,
    stop
  }
}

使用示例:

vue 复制代码
<template>
  <div ref="targetEl" :style="{ resize: 'both', overflow: 'auto' }">
    可调整大小的元素
  </div>
  <p>宽度: {{ width }}px</p>
  <p>高度: {{ height }}px</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useElementSize } from './useElementSize'

const targetEl = ref<HTMLElement | null>(null)

const { width, height, stop } = useElementSize(targetEl, {
  initialSize: { width: 200, height: 100 },
  box: 'border-box'
})

// 手动停止监听
// setTimeout(stop, 5000)
</script>

实现细节说明:

  1. 核心功能:

    • 使用现代浏览器 ResizeObserver API 实现高性能尺寸监听
    • 支持 content-box 和 border-box 两种测量模式
    • 自动处理组件卸载时的资源清理
  2. 响应式数据:

    • width: 响应式宽度值
    • height: 响应式高度值
    • 自动更新机制:当元素尺寸变化时,自动更新尺寸数据
  3. 高级特性:

    • 支持初始尺寸设置 (initialSize)
    • 提供手动停止监听方法 (stop())
    • 自动处理 Vue 组件实例的 DOM 元素获取
  4. 性能优化:

    • 自动断开旧元素的监听
    • 使用 ResizeObserver 的现代浏览器特性
    • 自动清理观察器实例
  5. 类型安全:

    • 完整 TypeScript 类型支持
    • 兼容 Vue 组件实例和原生 DOM 元素
    • 严格的参数类型校验

配置选项说明:

参数 类型 默认值 说明
initialSize { width?: number; height?: number } {} 初始尺寸值,用于元素挂载前的默认值
box 'content-box' | 'border-box' 'content-box' 测量模式: - content-box: 内容区域尺寸 - border-box: 包含边框尺寸

实现亮点:

  1. 智能元素获取:

    typescript 复制代码
    const getElement = () => {
      const el = unref(target)
      return (el as ComponentPublicInstance)?.$el ?? el
    }

    自动处理 Vue 组件实例,获取其对应的 DOM 元素

  2. 尺寸计算优化:

    typescript 复制代码
    const boxSize = entry[box === 'border-box' ? 'borderBoxSize' : 'contentBoxSize']

    根据配置选项使用不同的尺寸计算方式,提供更精确的尺寸数据

  3. 自动清理机制:

    typescript 复制代码
    watch(() => getElement(), (newEl, oldEl, onCleanup) => {
      // ...
      onCleanup(() => disconnectObserver())
    })

    使用 watch 的清理回调函数确保元素切换时的资源释放

  4. 跨浏览器兼容:

    typescript 复制代码
    const hasBorderBoxSize = box === 'border-box' && boxSize
    if (hasBorderBoxSize) {
      // 现代浏览器支持
    } else {
      // 兼容旧版本浏览器
    }

    同时支持现代浏览器的新特性和旧浏览器的 fallback 方案

使用场景建议:

  1. 响应式布局组件
  2. 图表/可视化组件
  3. 自适应内容区域
  4. 拖拽调整大小功能
  5. 媒体元素尺寸监控

该实现提供了与 VueUse 相似的开发体验,同时保持了以下优势:

  • 更小的体积(仅核心功能)
  • 可定制的测量模式
  • 明确的类型定义
  • 更好的浏览器兼容性处理
相关推荐
羲云9 分钟前
Claude 越界事件之后:Agent 权限设计为什么不能只靠提示词
前端
行者全栈架构师22 分钟前
【鸿蒙心迹】鸿蒙网络请求架构实战——@ohos.net.http 到 Axios 封装、拦截器与统一错误处理(HarmonyOS 7.x)
前端·算法·架构
全栈Agent 小李29 分钟前
【无标题】
前端·后端·agent·ai编程·全栈·cursor·mcp
羲云30 分钟前
别让 AI 凭记忆做调研:给 WorkBuddy 接上可核验的实时搜索
前端
deli0071 小时前
连接池到底配多大?QPS 和 RT 算一遍,再跑 1 万次请求实测超时率
前端
风花一世月1 小时前
🏮 我把三坊七巷做成了能飞的 3D 地图(901 个真实建筑轮廓、20 处景点,在线直接玩)
前端
特立独行的猫A1 小时前
用仓颉语言写一个串口调试助手:cj-tauri 实战
前端
小凯在掘金1 小时前
Shadow DOM 和 Virtual DOM ?别再傻傻分不清
前端·html
郑州光合科技余经理1 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
数据掘金1 小时前
埋点事件命名升级时老数据怎么办
前端