VantuseRect组合式函数完全指南:获取元素尺寸与视口相对位置
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
useRect是@vant/use提供的一个轻量级组合式函数(Composable),用于获取任意 DOM 元素的尺寸,以及该元素相对于浏览器视口(viewport)的位置,其行为等价于原生Element.getBoundingClientRect()。它被广泛用于 Vant 组件库内部(如 Sticky、List、Calendar、DropdownMenu、IndexBar 等组件)以完成布局测量与滚动联动,本文将从基础用法、API 声明、源码实现到组件级实战应用,带你完整掌握这一工具。
功能概述
useRect的核心价值在于:在 Vue 3 的组合式 API 环境下,以统一的接口读取元素的布局信息。它的输入既可以是 DOM 元素本身,也可以是 Vue 的ref引用,内部通过unref自动解包,调用方无需关心传参形态;同时它还额外处理了Window对象与"元素尚未挂载"两种边界情况,避免直接调用getBoundingClientRect时的各种坑。
它与原生 API 的对应关系如下:
useRect(element) | 等价的原生调用 |
|---|---|
传入Element | element.getBoundingClientRect() |
传入Ref<Element> | unref(ref).getBoundingClientRect() |
传入Window | 基于innerWidth/innerHeight构造的DOMRect |
传入undefined或空 ref | 返回全 0 的DOMRect |
安装与引入
@vant/use已经被包含在 Vant 的依赖中,但官方仍然推荐在项目中显式安装该包,以便直接使用这些组合式 API(详见 Composables 文档):
# with npm npm i @vant/use # with yarn yarn add @vant/use # with pnpm pnpm add @vant/use # with Bun bun add @vant/use安装完成后,从包入口导入即可(useRect由 入口文件 统一导出):
import { useRect } from '@vant/use';基础用法
传入元素引用(ref)
在模板中给目标元素绑定ref,然后在onMounted生命周期中调用useRect(此时元素已完成挂载,测量才有意义):
<div ref="root" />import { ref, onMounted } from 'vue'; import { useRect } from '@vant/use'; export default { setup() { const root = ref(); onMounted(() => { const rect = useRect(root); console.log(rect); // -> the size of an element and its position relative to the viewport }); return { root }; }, };传入原始 DOM 元素
也可以直接传入元素实例,这在事件回调、指令或第三方库集成场景中很常见:
import { useRect } from '@vant/use'; const el = document.querySelector('.my-element'); const rect = useRect(el);传入 Window 对象
useRect也接受Window,此时返回的是整个窗口的尺寸(top、left恒为 0):
import { useRect } from '@vant/use'; const rect = useRect(window); // rect.width === window.innerWidth // rect.height === window.innerHeightAPI
类型声明
function useRect( element: Element | Window | Ref<Element | Window | undefined>, ): DOMRect;参数element支持三种形态:
Element:直接传入 DOM 元素;Window:传入全局window对象;Ref<Element | Window | undefined>:Vue 的 ref 引用,元素可能暂未挂载(值为undefined)。
返回值统一为标准的DOMRect对象。
返回值
| 名称 | 说明 | 类型 |
|---|---|---|
| width | 元素的宽度 | number |
| height | 元素的高度 | number |
| top | 元素顶部到视口顶部的距离 | number |
| left | 元素左侧到视口左侧的距离 | number |
| right | 元素右侧到视口左侧的距离(即left + width) | number |
| bottom | 元素底部到视口顶部的距离(即top + height) | number |
注意:
top/left表示的是元素边框到视口边缘的距离,而right/bottom是到视口左上角的绝对坐标,并非"元素到视口右边/下边的距离"。这与原生getBoundingClientRect()的语义完全一致,页面发生滚动时这些值会随之变化。
源码实现深度解析
useRect的实现非常精简,完整源码位于 packages/vant-use/src/useRect/index.ts。理解它的三个分支,有助于你在边界场景下正确使用:
import { Ref, unref } from 'vue'; const isWindow = (val: unknown): val is Window => val === window; const makeDOMRect = (width: number, height: number) => ({ top: 0, left: 0, right: width, bottom: height, width, height, }) as DOMRect; export const useRect = ( elementOrRef: Element | Window | Ref<Element | Window | undefined>, ) => { const element = unref(elementOrRef); if (isWindow(element)) { const width = element.innerWidth; const height = element.innerHeight; return makeDOMRect(width, height); } if (element?.getBoundingClientRect) { return element.getBoundingClientRect(); } return makeDOMRect(0, 0); };1.unref自动解包 ref
首行调用 Vue 的unref:如果传入的是 ref,则取其.value;如果传入的是普通元素或Window,则原样返回。这正是"传元素或传 ref 皆可"这一便利性的来源。
2.Window特殊分支
isWindow通过val === window严格判断。由于window对象自身没有getBoundingClientRect方法,如果直接调用会抛出TypeError,因此源码用innerWidth/innerHeight构造了一个DOMRect:此时top、left为 0,right等于窗口宽度,bottom等于窗口高度。
3.getBoundingClientRect主路径
对于普通元素,直接返回element.getBoundingClientRect()的结果,确保返回值与原生 API 完全一致(包含x、y等全部标准字段)。
4. 全零兜底
当传入undefined(例如 ref 尚未绑定到已挂载元素,或元素被销毁)时,返回一个width、height、top、left、right、bottom全部为 0 的DOMRect,保证调用方无需做空值判断,也不会因调用不存在的方法而抛错。
在 Vant 组件中的真实应用
useRect不是孤立存在的工具函数,Vant 大量组件都基于它完成布局测量。以下是两个具有代表性的应用场景。
场景一:Sticky 吸顶组件的定位判断
在 Sticky 组件 中,滚动事件回调onScroll(Sticky.tsx)通过useRect同时测量根元素与容器元素的位置,决定是否进入fixed吸顶状态:
const onScroll = () => { if (!root.value || isHidden(root)) { return; } const { container, position } = props; const rootRect = useRect(root); const scrollTop = getScrollTop(window); state.width = rootRect.width; state.height = rootRect.height; if (position === 'top') { if (container) { const containerRect = useRect(container); const difference = containerRect.bottom - offset.value - state.height; state.fixed = offset.value > rootRect.top && containerRect.bottom > 0; state.transform = difference < 0 ? difference : 0; } else { state.fixed = offset.value > rootRect.top; } } // ... };这里rootRect.width被用于同步吸顶时占位元素的宽度,避免页面布局抖动;rootRect.top与containerRect.bottom则分别驱动吸顶触发与容器内回收。窗口尺寸变化时(Sticky.tsx),组件同样依赖useRect(root)重新测量,保证吸顶宽度跟随响应式布局更新。
场景二:动态高度测量
use-height组合式函数 封装了"元素高度自动追踪"逻辑,其核心正是useRect:
const setHeight = () => { height.value = useRect(element).height; };它会在onMounted、Popup 重新打开、窗口尺寸变化等时机反复调用useRect刷新高度值,并在需要适配安全区(safe area)时通过定时器补偿 iOS 上首屏高度测量不准的问题。
更多使用位置
通过源码检索可以发现,useRect还被下列组件直接引用,覆盖滚动联动、弹层定位、懒加载检测等场景:
- List.tsx:列表滚动加载时的占位与状态判断;
- Calendar.tsx 与 CalendarMonth.tsx:日历滚动定位;
- DropdownMenu.tsx 与 DropdownItem.tsx:下拉菜单的弹出层定位;
- IndexBar.tsx 与 IndexAnchor.tsx:索引栏锚点定位;
- FloatingBubble.tsx、Signature.tsx、SwipeCell.tsx 等。
这些组件在渲染与交互的关键路径上调用useRect,验证了它在真实业务中的高频价值。
使用注意事项与最佳实践
- 在挂载后再测量:
getBoundingClientRect要求元素存在于文档中。请务必在onMounted或nextTick之后调用,否则空 ref 会走全零兜底分支,得到width: 0、height: 0。 - 与滚动的关系:返回值是相对视口的坐标,页面滚动会导致
top/left变化。若需要元素在文档中的绝对位置,需叠加滚动偏移量。 - 避免在布局抖动中高频调用:
getBoundingClientRect会强制浏览器进行样式计算与布局(reflow),在滚动事件中频繁调用可能影响性能。可参考 Sticky 组件"仅在scroll事件回调中测量,其余时机复用缓存值"的做法。 - 隐藏元素返回 0:使用
display: none等隐藏元素测量结果恒为 0,Vant 的isHidden工具会配合跳过此类场景;如果你需要测量隐藏元素的尺寸,请先使其可见。 - 服务端渲染(SSR)注意:
useRect依赖window与 DOM API,仅应在客户端执行。若需在 SSR 环境下使用,应将其放入onMounted等客户端生命周期中,避免在服务端渲染阶段直接调用。
总结
useRect用不到 20 行代码,为 Vue 开发者提供了对getBoundingClientRect的优雅封装:自动解包 ref、兼容Window、空值兜底,返回值保持标准DOMRect语义。无论是独立业务开发中的元素测量,还是深入阅读 Vant 源码时理解 Sticky、List、DropdownMenu 等组件的定位逻辑,掌握它都能让你的 Vue 3 开发事半功倍。若想系统了解@vant/use提供的其他组合式 API(如useScrollParent、useCountDown、useEventListener等),可参阅 Composables 总览文档。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考