2026/9/12 1:41:13

Vant useRect 组合式函数完全指南:获取元素尺寸与视口位置的正确姿势

Vant useRect 组合式函数完全指南:获取元素尺寸与视口位置的正确姿势 Vant useRect 组合式函数完全指南获取元素尺寸与视口位置的正确姿势【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant导读useRect是 Vant 移动端组件库配套的 Composition API 工具库 vant/use 中提供的一个轻量级组合式函数用于获取元素的大小及其相对于视口viewport的位置其行为等价于原生Element.getBoundingClientRect()。在移动端 H5 页面中无论是实现吸顶导航、滚动监听、懒加载还是计算弹层定位都离不开对元素几何信息的精确读取。读完本文你将掌握useRect的完整 API、返回值语义、源码级实现原理并学会如何在自己项目中通过它优雅地完成各类元素定位与尺寸计算场景。一、为什么需要 useRect在前端日常开发中读取一个元素的宽高和位置通常有两种方式使用offsetWidth/offsetHeight获取布局尺寸使用getBoundingClientRect()一次性获取元素相对于视口的完整矩形信息。getBoundingClientRect()返回的是一个DOMRect对象包含width、height、top、left、right、bottom六个核心数值属性。它虽然强大但在 Vue 项目中直接使用时存在几个小麻烦需要手动处理响应式状态与 DOM 节点之间的绑定关系传入的参数既可能是 DOM 元素也可能是模板ref生成的响应式引用需要手动unref当目标是window对象时getBoundingClientRect并不存在需要额外分支处理当目标元素尚未渲染或获取失败时缺少安全的兜底返回值。useRect正是针对这些痛点设计的封装它统一了「元素 / Window / 响应式 ref」三种入参形态并为异常场景提供了安全的默认返回值让你只需一行代码即可拿到标准的DOMRect。二、快速上手2.1 安装依赖useRect属于vant/use工具包需要先安装它。Vant 主库内部也直接依赖该包如 Sticky.tsx 中import { useRect, useEventListener, useScrollParent } from vant/use因此安装过 Vant 的项目通常已经具备该依赖无需重复安装# npm npm install vant/use # pnpmVant 仓库自身使用 pnpm 管理 pnpm add vant/use根据 vant/use 的 package.json它要求vue ^3.0.0作为 peer 依赖仅适用于 Vue 3 项目同时sideEffects: false意味着该库可以被构建工具安全地进行 tree-shaking按需引入useRect不会产生多余代码。2.2 基本用法在组件模板中为需要测量的元素绑定ref然后在生命周期钩子如onMounted中调用useRectdiv refroot /import { ref, onMounted } from vue; import { useRect } from vant/use; export default { setup() { const root ref(); onMounted(() { const rect useRect(root); console.log(rect); // - 元素的大小及其相对于视口的位置 }); return { root }; }, };这段示例来自官方文档 use-rect.zh-CN.md需要注意几个细节useRect直接接收模板ref即root这个 Ref 对象作为参数无需先取root.value函数内部会自行解包读取尺寸务必放在onMounted之后因为此时 DOM 节点才真正挂载完成、ref才被赋值若在setup同步阶段调用得到的是兜底的空矩形详见下文源码解析。三、API 详解3.1 类型定义useRect的完整类型签名如下与 useRect/index.ts 中的实际定义一致function useRect( element: Element | Window | RefElement | Window | undefined, ): DOMRect;它接受三类入参入参形态说明Element直接的 DOM 元素如document.querySelector(.foo)的返回值Window全局window对象用于获取视口尺寸RefElement \| Window \| undefinedVue 模板 ref 或任意响应式引用内部通过unref自动解包值为undefined如元素尚未挂载时安全返回空矩形返回值类型统一为标准的DOMRect与浏览器原生getBoundingClientRect()的返回结构完全一致可直接沿用你熟悉的 DOM 几何计算习惯。3.2 返回值说明参数说明类型width宽度numberheight高度numbertop顶部与视图窗口左上角的距离numberleft左侧与视图窗口左上角的距离numberright右侧与视图窗口左上角的距离numberbottom底部与视图窗口左上角的距离number需要注意的是这里的top/left/right/bottom都是相对于当前视口左上角的坐标而非相对于页面文档顶部。当页面发生滚动时这些值会随之变化——这正是「吸顶」类组件需要反复在滚动事件中重新读取useRect的原因详见第五节的应用案例。3.3 返回值的边界语义当入参为window时返回的矩形表示整个视口width/height即innerWidth/innerHeight而top、left恒为 0当入参对应的元素不存在ref 尚未绑定或值为undefined时返回一个width: 0, height: 0且top/left/right/bottom均为 0 的「空矩形」不会抛出异常方便在组件未挂载时安全调用。四、源码级原理剖析useRect的完整实现只有短短 31 行见 packages/vant-use/src/useRect/index.ts但其中包含了几处值得玩味的设计。4.1 入参统一解包unrefconst element unref(elementOrRef);函数第一步就是对入参执行unref。unref是 Vue 3 提供的工具函数如果传入的是 Ref 对象则返回其.value否则原样返回。这使得useRect既可以接收原生 DOM 元素也可以直接接收模板 ref调用方无需关心入参形态这也是 Vue 3 组合式 API 生态中的通用约定。4.2 Window 特判与空值兜底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;核心逻辑包含三个分支Window 分支通过val window严格判断入参是否为全局对象。由于window没有getBoundingClientRect方法这里单独取出innerWidth与innerHeight构造一个top: 0、left: 0、right: width、bottom: height的矩形语义上等价于「整个视口」普通元素分支if (element?.getBoundingClientRect)先做能力检测存在该方法则直接委托给原生实现保证返回结果与浏览器行为完全一致包含小数精度、x/y等扩展属性兜底分支当元素不存在或不可测量时返回makeDOMRect(0, 0)构造的空矩形避免调用方对undefined做防御性判断。从源码结构可以推断makeDOMRect之所以手工构造而非调用new DOMRect()一方面是为了兼容不支持DOMRect构造函数的旧环境另一方面也让 Window 与空值两个分支共用同一构造逻辑代码更加精简。4.3 与原生 API 的等价关系当入参是普通元素时useRect(element)与element.getBoundingClientRect()返回的矩形信息完全一致——两者最终都来自浏览器的布局引擎。useRect的意义在于统一了入参形态并补齐了异常分支而不是重新实现一套几何计算。这与官方文档中「等价于Element.getBoundingClientRect」的定位完全吻合。五、在 Vant 组件库中的真实应用useRect并非一个孤立的演示工具它深度参与了 Vant 主库多个组件的核心逻辑。通过观察这些真实用法可以帮你判断在自己的业务代码中应该如何使用它。5.1 Sticky 吸顶组件的滚动定位Sticky.tsx 是useRect最典型的应用场景。在其滚动处理函数onScroll中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.top因为当元素滚动出视口顶部时才需要切换为固定定位。每次滚动事件中重新调用useRect获取最新位置这正是getBoundingClientRect相对视口坐标语义的实际应用。同时在监听窗口尺寸变化watch([windowWidth, windowHeight], ...)后组件也会通过nextTick重新读取useRect(root)来刷新宽高状态。5.2 useHeight组件内部的高度测量封装Vant 内部在 composables/use-height.ts 中对useRect做了二次封装const setHeight () { height.value useRect(element).height; }; onMounted(() { nextTick(setHeight); // 安全区场景下页面加载时系统可能无法返回正确高度延迟重试 if (withSafeArea) { for (let i 1; i 3; i) { setTimeout(setHeight, 100 * i); } } }); // popup 隐藏时高度可能为 0重新打开后需要重置 onPopupReopen(() nextTick(setHeight)); // 窗口尺寸变化时元素高度可能变化 watch([windowWidth, windowHeight], setHeight);这段代码揭示了三个实战经验测量时机必须在nextTick之后测量确保 DOM 更新完成特殊环境补偿iPhone 安全区safe area场景下页面加载初期系统可能返回不正确的尺寸需要延时多次重测状态变化重测弹层重新打开、窗口尺寸变化都会导致测量结果失效需要建立对应的重测钩子。5.3 滚动定位工具getElementTop在 utils/dom.ts 中useRect被用来计算元素相对页面顶部的距离// get distance from element top to page top or scroller top export function getElementTop(el: ScrollElement, scroller?: ScrollElement) { if (el window) { return 0; } const scrollTop scroller ? getScrollTop(scroller) : getRootScrollTop(); return useRect(el).top scrollTop; }这里演示了一个重要换算技巧getBoundingClientRect返回的top是相对视口的坐标要得到相对页面文档顶部的坐标只需加上当前滚动距离scrollTop即可。该函数被 IndexBar 索引栏、Lazyload 懒加载等组件的滚动定位逻辑广泛依赖。5.4 更多使用场景从仓库检索结果看useRect还被大量组件引用例如 Calendar.tsx、DropdownMenu.tsx、FloatingBubble.tsx、IndexAnchor.tsx、List.tsx、NoticeBar.tsx、Slider.tsx、SwipeCell.tsx 等覆盖了锚点定位、滚动加载、轮播偏移、滑动距离计算等多种几何场景。这充分说明凡是需要「元素相对视口的位置 尺寸」的地方都可以用useRect统一搞定。六、进阶实践建议6.1 与滚动事件配合时的性能注意由于useRect底层即getBoundingClientRect而该 API 会触发浏览器强制同步布局forced reflow在高频滚动、拖拽场景中应避免无节制的调用。Vant 自身的做法值得借鉴见 Sticky.tsx先在滚动回调中通过isHidden(root)快速过滤不可见元素display: none等场景再测量只在状态确实可能变化时才读取useRect避免重复计算涉及窗口尺寸变化的重测放在nextTick中执行。6.2 何时应返回 0 值在组件初始化、弹层关闭等「元素不可见或未挂载」的间隙调用useRect得到的是width: 0, height: 0的空矩形这是设计预期而非 bug。业务上应将其视为「测量无效」信号待元素可见后再重新测量参考 use-height.ts 中onPopupReopen(() nextTick(setHeight))的重测思路。6.3 在 Vue 3 组合式写法中使用如果你使用script setup语法基本用法同样简洁script setup import { ref, onMounted } from vue; import { useRect } from vant/use; const root ref(); onMounted(() { const { top, left, width, height } useRect(root); console.log({ top, left, width, height }); }); /script template div refroot classtarget目标元素/div /template七、总结useRect以 31 行源码完成了对原生getBoundingClientRect的「入参统一 异常兜底 Window 支持」三件事统一入参Element、Window、Ref 三种形态一行调用免去手动unref安全兜底元素未挂载时返回空矩形不抛异常视口语义返回值与原生 API 完全一致top/left/right/bottom均为相对视口坐标配合scrollTop即可换算为文档坐标。它既是 vant/use 工具库对外导出的基础能力见 index.ts也是 Vant 主库 Sticky、IndexBar、List 等十余个组件内部几何计算的基石。在你的 Vue 3 业务项目中遇到任何需要读取元素尺寸或位置的场景都可以优先考虑使用useRect让代码更简洁、边界更安全。【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考