radix-vue(Reka UI)TooltipContent 组件深度解析:16 个 Props、事件与碰撞定位原理实战

radix-vue(Reka UI)TooltipContent 组件深度解析:16 个 Props、事件与碰撞定位原理实战 radix-vueReka UITooltipContent 组件深度解析16 个 Props、事件与碰撞定位原理实战【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vueTooltipContent 是 radix-vueReka UI工具提示Tooltip组件族中的核心弹出部件负责在触发器聚焦或悬停时展示信息浮层。本文以 TooltipContent 官方元数据文档 为骨架逐项解读其全部 Props 与事件并结合packages/core/src/Tooltip与packages/core/src/Popper的源码剖析其基于 Floating UI 的定位、碰撞检测与动画机制最终给出可复制运行的完整实战代码。读完本文你将能够精确控制 TooltipContent 的方位、偏移、碰撞行为、挂载时机与无障碍语义。TooltipContent 在 Tooltip 组件族中的角色Tooltip 是一组协同工作的部件Parts官方推荐的最小组合结构如下完整用法见 tooltip.mdscript setup langts import { TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from reka-ui /script template TooltipProvider TooltipRoot TooltipTrigger / TooltipPortal TooltipContent TooltipArrow / /TooltipContent /TooltipPortal /TooltipRoot /TooltipProvider /template其中TooltipContent是弹出内容本体。从源码结构看TooltipContent.vue它的渲染链路为TooltipContentPresence 包裹 └─ TooltipContentHoverable可悬停内容/ TooltipContentImpl默认 └─ DismissableLayer点击外部 / Esc 关闭 └─ PopperContentFloating UI 定位 └─ 具名插槽内容 VisuallyHidden无障碍朗读文本选择哪条渲染路径由TooltipRoot的disableHoverableContent决定启用时使用 TooltipContentImpl.vue禁用悬停内容时才切换到 TooltipContentHoverable.vue后者借助useGraceArea维护指针在触发器和内容之间过渡的宽限区域避免悬停中断导致闪烁关闭。Props 全解析16 个配置项逐项拆解以下表格完整继承自 TooltipContent.md字段、类型与必填性均以文档为准NameDescriptionTypeRequiredDefaultalignThe preferred alignment against the trigger. May change when collisions occur.start \| center \| endNo-alignOffsetAn offset in pixels from the start or end alignment options.numberNo-ariaLabelBy default, screenreaders will announce the content inside the component. If this is not descriptive enough, or you have content that cannot be announced, use aria-label as a more descriptive label.stringNo-arrowPaddingThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners.numberNo-asThe element or component this component should render as. Can be overwritten by asChild.AsTag \| ComponentNodivasChildChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details.booleanNo-avoidCollisionsWhen true, overrides the side and align preferences to prevent collisions with boundary edges.booleanNo-collisionBoundaryThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check.Element \| (Element \| null)[] \| nullNo-collisionPaddingThe distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }.number \| PartialRecordtop \| right \| bottom \| left, numberNo-forceMountUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.booleanNo-hideWhenDetachedWhether to hide the content when the trigger becomes fully occluded.booleanNo-positionStrategyThe type of CSS position property to use.fixed \| absoluteNo-sideThe preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled.top \| right \| bottom \| leftNo-sideOffsetThe distance in pixels from the trigger.numberNo-stickyThe sticky behavior on the align axis. partial will keep the content in the boundary as long as the trigger is at least partially in the boundary whilst always will keep the content in the boundary regardless.partial \| alwaysNo-updatePositionStrategyStrategy to update the position of the floating element on every animation frame.always \| optimizedNo-定位偏好类side / sideOffset / align / alignOffsetside定义内容相对触发器的首选方位上/右/下/左sideOffset定义与触发器的像素间距。align定义在主轴上的对齐方式start/center/endalignOffset在start/end对齐基础上再追加像素偏移用于微调。一个直观组合示例sidetop、side-offset5、alignstart、align-offset8表示内容出现在触发器上方 5px 处向起点侧对齐并再偏移 8px。碰撞处理类avoidCollisions / collisionBoundary / collisionPadding / sticky / hideWhenDetached这组 Props 控制浮层在视口或自定义容器边缘放不下时的行为是 TooltipContent 在复杂页面布局中不越界的关键avoidCollisions为true时会覆盖side/align的偏好值主动翻转方向以避让边界默认开启。collisionBoundary碰撞检测的边界元素。默认是视口viewport也可以传入一个或多个元素数组将其纳入检测范围。collisionPadding边界四周保留的安全距离像素。传数字表示四边一致也支持部分对象如{ top: 20, left: 20 }。sticky对齐轴上的粘性策略。partial表示只要触发器还在边界内哪怕只有一部分内容就尽量留在边界内always则无论触发器位置如何都强制内容留在边界内。hideWhenDetached当触发器被完全遮挡如滚出视口时隐藏内容。渲染行为类positionStrategy / updatePositionStrategy / forceMountpositionStrategy定位使用的 CSSposition类型fixed或absolute。updatePositionStrategy浮层位置更新策略。optimized默认按需更新always则在每个动画帧都重新计算——适用于浮层内容本身带有高频动画的场景。forceMount强制挂载内容不受开关状态控制。在 TooltipContent.vue 中Presence的present属性为forceMount || rootContext.open.value因此它常与 Vue 动画库配合实现离场动画控制。无障碍与组合类ariaLabel / as / asChild / arrowPaddingariaLabel默认情况下读屏软件会朗读内容区内的文本若内容本身不可读或不够描述性可用该属性提供更完整的无障碍标签源码实现在下文详述。as组件实际渲染的标签默认divasChild则完全复用子元素的标签并合并 Props 与行为即Composition组合模式官方文档建议配合 Composition 指南 使用。arrowPadding箭头与内容边缘之间的内边距。当内容带有border-radius时该值可防止箭头溢出圆角边缘。默认值速查文档表格中Default列多为-实际默认值定义在 TooltipContentImpl.vue 的defu合并逻辑中属性默认值sidetopsideOffset0aligncenteravoidCollisionstruecollisionBoundary[]即仅视口collisionPadding0arrowPadding0stickypartialhideWhenDetachedfalsepositionStrategyfixedupdatePositionStrategyoptimizedEvents两个可阻止的关闭事件TooltipContent 仅暴露两个事件均用于关闭时机的精细化拦截可调用event.preventDefault()阻止默认行为NameDescriptionTypeescapeKeyDownEvent handler called when focus moves to the destructive action after opening. It can be prevented by calling event.preventDefault[event: KeyboardEvent]pointerDownOutsideEvent handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling event.preventDefault.[event: Event]它们在 TooltipContentImpl.vue 中由DismissableLayer逐层转发escape-key-down与pointer-down-outside直接透传而dismiss关闭确认则触发rootContext.onClose()。例如若希望点击内容区外部时不关闭提示可在pointerDownOutside中event.preventDefault()。TooltipContent pointer-down-outside(event) event.preventDefault() 点击外部也不关闭 /TooltipContent源码级原理TooltipContent 的四大内部机制1. Presence 与 forceMountTooltipContent.vue 外层包裹Presence组件present值由forceMount || rootContext.open.value计算。这意味着常规情况下内容随open状态挂载/卸载设置forceMount后内容常驻 DOM配合 CSS 动画库可实现完整的进入 离开双向动画控制。2. 无障碍朗读ariaLabel 与 VisuallyHidden在 TooltipContentImpl.vueariaLabel的默认值并非空串而是const ariaLabel computed(() props.ariaLabel || currentElement.value?.textContent)即未显式传入ariaLabel时自动取内容区文本作为朗读内容。该文本通过VisuallyHidden渲染为roletooltip、id为contentId的隐藏节点第 121-126 行而 TooltipTrigger.vue 在打开状态下为触发器设置aria-describedby指向该contentId从而建立完整的无障碍关联关系。3. 自动关闭滚动监听与互斥机制TooltipContentImpl 挂载后注册了两个全局监听第 82-91 行监听window的scrollcapture 阶段若滚动目标是触发器或其祖先则关闭当前提示监听自定义事件TOOLTIP_OPEN见 TooltipRoot.vue 中打开时派发保证同一时刻只有一个 Tooltip 处于打开状态。4. 定位引擎Floating UI 中间件链TooltipContent 的位置计算完全复用 Popper 体系。在 PopperContent.vue 中computedMiddleware组装了一条中间件链与 TooltipContent 各 Props 一一对应offset由sideOffset arrowHeight主轴与alignOffset交叉轴驱动flip由avoidCollisions与sideFlip/alignFlip控制负责碰撞时翻转方位与对齐shift由avoidCollisions驱动sticky partial时附加limitShift()限制器size将availableWidth/availableHeight、锚点宽高等写入 CSS 变量见下节arrow以arrowPadding为内边距计算箭头位置transformOrigin计算内容与箭头相对位置得出transform-originhide仅当hideWhenDetached为true时启用referenceHidden策略。同时autoUpdate的animationFrame参数由updatePositionStrategy always控制第 347-352 行strategy则由positionStrategy传入。此外Provider 层还可通过content属性提供全局默认配置TooltipContentImpl.vue 使用defu将 Props Provider 默认值 组件内置默认值三级合并。CSS 变量与 data 属性动画与尺寸约束的钥匙data 属性TooltipContent 暴露三个运行时可变的 data 属性官方文档 tooltip.md属性取值[data-state]closed/delayed-open/instant-open[data-side]left/right/bottom/top[data-align]start/end/center其中data-state的三态由 TooltipRoot.vue 计算未打开为closed打开时若经历过延迟定时器则为delayed-open否则为instant-open。data-side/data-align会随碰撞翻转实时更新placedSide/placedAlign见 PopperContent.vue因此可用来编写方向感知动画。CSS 变量TooltipContentImpl 将 Popper 的计算结果映射为五个专属变量TooltipContentImpl.vueCSS 变量含义--reka-tooltip-content-transform-origin由内容与箭头位置/偏移计算出的transform-origin--reka-tooltip-content-available-width触发器与边界之间剩余的宽度--reka-tooltip-content-available-height触发器与边界之间剩余的高度--reka-tooltip-trigger-width触发器的宽度--reka-tooltip-trigger-height触发器的高度实战示例基础用法带箭头script setup import { TooltipArrow, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from reka-ui /script template TooltipProvider TooltipRoot TooltipTrigger悬停我/TooltipTrigger TooltipContent :side-offset5 classTooltipContent 提示内容 TooltipArrow :width11 :height5 / /TooltipContent /TooltipRoot /TooltipProvider /template全局统一延迟利用 TooltipProvider 的delayDuration默认 700ms与skipDelayDuration默认 300ms统一控制所有提示的打开节奏TooltipProvider :delay-duration800 :skip-delay-duration500 TooltipRoot…/TooltipRoot TooltipRoot…/TooltipRoot /TooltipProvider若某个提示需要立即弹出可在 Root 上覆写TooltipRoot :delay-duration0。约束内容尺寸使用 CSS 变量让内容宽度贴合触发器、高度不超视口TooltipContent classTooltipContent :side-offset5…/TooltipContent.TooltipContent { width: var(--reka-tooltip-trigger-width); max-height: var(--reka-tooltip-content-available-height); }从计算原点展开的动画.TooltipContent { transform-origin: var(--reka-tooltip-content-transform-origin); animation: scaleIn 0.5s ease-out; } keyframes scaleIn { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }碰撞方向感知动画.TooltipContent { animation-duration: 0.6s; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); } .TooltipContent[data-sidetop] { animation-name: slideUp; } .TooltipContent[data-sidebottom] { animation-name: slideDown; } keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } keyframes slideUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }为禁用按钮显示提示禁用按钮不触发任何事件需将 Trigger 渲染为span并让按钮忽略指针事件官方文档 tooltip.mdTooltipRoot TooltipTrigger as-child span tabindex0 button disabled style{ pointerEvents: none }…/button /span /TooltipTrigger TooltipContent…/TooltipContent /TooltipRoot无障碍与键盘交互Tooltip 遵循 WAI-ARIA Tooltip 设计模式官方文档 tooltip.md。键盘交互如下按键行为Tab无延迟地打开/关闭提示Space若已打开无延迟关闭Enter若已打开无延迟关闭Escape若已打开无延迟关闭再加上ariaLabelVisuallyHiddenaria-describedby的自动关联以及 TooltipRoot 提供的ignoreNonKeyboardFocus仅键盘焦点打开提示等无障碍增强选项TooltipContent 在默认配置下即可获得完整的读屏支持。自定义 API 封装Tooltip 全部部件都支持asChild组合模式可将多个部件抽象成自己的组件并暴露更简洁的 Props。官方文档给出了把内容抽成contentprop 的示例tooltip.md!-- your-tooltip.vue -- script setup langts import type { TooltipRootEmits, TooltipRootProps } from reka-ui import { TooltipArrow, TooltipContent, TooltipRoot, TooltipTrigger, useForwardPropsEmits } from reka-ui const props definePropsTooltipRootProps { content?: string }() const emits defineEmitsTooltipRootEmits() const forward useForwardPropsEmits(props, emits) /script template TooltipRoot v-bindforward TooltipTrigger as-child slot / /TooltipTrigger TooltipContent sidetop aligncenter {{ content }} TooltipArrow :width11 :height5 / /TooltipContent /TooltipRoot /template使用时即可获得极简 APITooltip content提示内容button触发器/button/Tooltip。这种封装方式充分体现了 TooltipContent 各 Props 的可组合性与默认值设计的合理性——即使不显式传入任何定位参数内容也会以sidetop、aligncenter的默认姿态配合avoidCollisions自动避让边界稳定呈现在用户视野内。【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考