ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

在 radix-vue(reka-ui)中使用 ContextMenu 构建右键上下文菜单

在 radix-vue(reka-ui)中使用 ContextMenu 构建右键上下文菜单 在 radix-vuereka-ui中使用 ContextMenu 构建右键上下文菜单【免费下载链接】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本文以 radix-vue 仓库中的 Context Menu 官方文档 为主体系统讲解如何用 radix-vue组件库包名现为reka-ui原 Radix Vue中无样式、可访问的ContextMenu组件族构建右键菜单覆盖组件安装、完整 Anatomy、全部 API Props/Events、常用实战示例子菜单、禁用项、勾选项、单选组、分隔线等、尺寸约束与动画方案以及键盘交互与无障碍实现细节。读完本文你将能够在 Vue 3 项目中独立搭建一个功能完整、符合 WAI-ARIA 规范、支持键盘导航与触屏长按触发的右键上下文菜单。核心特性一览ContextMenu的核心定位是位于指针位置、由右键或长按触发的菜单。根据文档给出的 Feature 清单它具备以下能力支持子菜单Submenu并可配置阅读方向LTR/RTL支持 Item、Label、Group 多种结构部件支持可勾选条目单选或多选可呈现indeterminate中间态支持模态modal与非模态两种交互模式可自定义 side弹出方位、align对齐方式、偏移量与碰撞collision处理焦点完全由组件管理完整的键盘导航支持 typeahead按字符快速定位Dismiss关闭与分层layering行为高度可定制触屏设备上支持长按触发。这些特性中触屏长按触发与定位在指针处正是ContextMenu区别于普通DropdownMenu的核心场景。安装在项目命令行中安装组件包文档中的安装指引即针对reka-ui包名pnpm add reka-ui # 或 npm install reka-ui组件结构AnatomyContextMenu采用组装式设计先导入所有部件再按需拼装。文档给出的完整骨架如下script setup langts import { ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuItemIndicator, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuRoot, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from reka-ui /script template ContextMenuRoot ContextMenuTrigger / ContextMenuPortal ContextMenuContent ContextMenuLabel / ContextMenuItem / ContextMenuGroup ContextMenuItem / /ContextMenuGroup ContextMenuCheckboxItem ContextMenuItemIndicator / /ContextMenuCheckboxItem ContextMenuRadioGroup ContextMenuRadioItem ContextMenuItemIndicator / /ContextMenuRadioItem /ContextMenuRadioGroup ContextMenuSub ContextMenuSubTrigger / ContextMenuPortal ContextMenuSubContent / /ContextMenuPortal /ContextMenuSub ContextMenuSeparator / /ContextMenuContent /ContextMenuPortal /ContextMenuRoot /template整体调用链清晰Root提供上下文 →Trigger包裹右键目标 →Portal将内容传送到body→Content承载菜单内容内容内部再自由组合 Item、Group、CheckboxItem、RadioGroup、Sub 等部件。在仓库中各部件源码位于 packages/core/src/ContextMenu其中 ContextMenuRoot.vue 通过provideContextMenuRootContext向子孙注入open、modal、dir、pressOpenDelay等上下文最终委托给MenuRoot实现通用菜单逻辑。API ReferenceContextMenu遵循 Menu WAI-ARIA 设计模式 目录下的对应元数据文件。Root包含整个右键菜单的所有部件。Prop说明类型必填默认值dir菜单的阅读方向。省略时继承全局ConfigProvider的设置否则默认 LTRltr \| rtl否-modal菜单的模态性。为true时外部元素交互被禁用屏幕阅读器只可见菜单内容boolean否truepressOpenDelay从按下触发器到菜单打开的时长毫秒number否700事件事件说明类型update:open打开状态变化时的回调[payload: boolean]从 ContextMenuRoot.vue 的源码可以看到modal: true与pressOpenDelay: 700的默认值确实来自withDefaults且open状态通过watch触发update:open事件对外同步。pressOpenDelay的 700ms 正是为了兼容长按触发的物理时间。Trigger包裹目标区域用户右键或使用相关键盘快捷键时菜单从该区域弹出。Prop说明类型必填默认值as渲染成的元素或组件可被asChild覆盖AsTag \| Component否spanasChild将默认渲染元素替换为传入的子元素并合并其 props 与行为boolean否-disabled为true时右键不会打开菜单同时恢复浏览器原生右键菜单boolean否false数据属性[data-state]取值为open/closed。Portal使用时将 Content 部分传送到body。Prop说明类型必填defer延迟解析 Teleport 目标直到应用其它部分挂载完成需 Vue 3.5.0boolean否disabled禁用 Teleport改为内联渲染boolean否forceMount强制挂载便于配合 Vue 动画库控制动画boolean否to对应 Vue 原生 Teleport 组件的:topropstring \| HTMLElement否Content右键菜单打开时弹出的内容部件。Prop说明类型必填默认值as/asChild渲染元素控制AsTag \| Component/boolean否div/ -sideFlip与边界碰撞时翻转到对侧boolean否-alignFlip与边界碰撞时翻转对齐方向仅在prioritizePosition为 true 时生效boolean否-avoidCollisions为true时覆盖 side 与 align 偏好避免与边界碰撞boolean否truecollisionBoundary碰撞检测边界元素默认是视口可传入额外元素Element \| (Element \| null)[] \| null否[]collisionPadding碰撞检测时距边界的安全距离支持数字或部分对象如{ top: 20, left: 20 }number \| PartialRecordtop \| right \| bottom \| left, number否0sticky对齐轴上的粘滞行为partial在触发器部分在边界内时保持内容在边界内always则无条件保持partial \| always否partialhideWhenDetached触发器完全被遮挡时是否隐藏内容boolean否falseloop为true时键盘导航在首尾项间循环boolean否-positionStrategyCSS position 策略fixed \| absolute否-prioritizePosition强制内容位于视口内可能与触发元素重叠boolean否-reference自定义/虚拟元素作为定位参考替代默认锚点ReferenceElement否-alignOffset相对start/end对齐的像素偏移number否0hideShiftedArrow无法将箭头对准参考元素中心时隐藏箭头boolean否-forceMount强制挂载boolean否-disableUpdateOnLayoutShift布局位移时是否禁用内容位置更新boolean否-memoDependencies使 memo 化内容子树失效的响应式依赖unknown[]否-事件均可preventDefault阻止默认行为事件说明类型closeAutoFocus关闭时自动聚焦回调[event: Event]escapeKeyDownEsc 键按下回调[event: KeyboardEvent]focusOutside焦点移出 DismissableLayer 回调[event: FocusOutsideEvent]interactOutside外部交互外部pointerdown或焦点移出回调[event: PointerDownOutsideEvent \| FocusOutsideEvent]pointerDownOutside外部pointerdown回调[event: PointerDownOutsideEvent]数据属性属性取值[data-state]open/closed[data-side]left/right/bottom/top[data-align]start/end/centerCSS 变量Content 与 SubContent 均暴露变量含义--reka-context-menu-content-transform-origin由内容与箭头位置/偏移计算出的transform-origin--reka-context-menu-content-available-width触发器与边界之间的剩余宽度--reka-context-menu-content-available-height触发器与边界之间的剩余高度--reka-context-menu-trigger-width触发器的宽度--reka-context-menu-trigger-height触发器的高度Arrow可选箭头元素用于在视觉上连接触发器子菜单触发项与内容。必须渲染在ContextMenu.Content内部。Prop说明类型默认值as/asChild渲染元素控制AsTag \| Component/booleansvg/ -width箭头宽度像素number10height箭头高度像素number5rounded渲染圆角版箭头与as/asChild不兼容boolean-Item承载菜单项的部件。Prop说明类型默认值as/asChild渲染元素控制AsTag \| Component/booleandiv/ -disabled为true时禁止用户与该条目交互boolean-textValue供 typeahead 使用的文本。默认取.textContent内容复杂或含非文本内容时使用此项string-事件事件说明类型select用户选中条目鼠标或键盘时回调在回调中调用event.preventDefault()可阻止菜单关闭[event: Event]数据属性[data-highlighted]高亮时出现、[data-disabled]禁用时出现。Group用于将多个ContextMenu.Item分组。与 Item 相同支持as/asChild且默认渲染为div。Label渲染标签不会被方向键聚焦。支持as/asChild。CheckboxItem可受控、像复选框一样渲染的条目。Prop说明类型as/asChild渲染元素控制div/ -disabled禁用交互booleanmodelValue受控勾选状态可作为v-model使用false \| true \| indeterminatetextValuetypeahead 用文本string事件事件说明类型select选中回调preventDefault可阻止关闭菜单[event: Event]update:modelValue值变化回调[payload: boolean]数据属性[data-state]checked/unchecked/indeterminate、[data-highlighted]、[data-disabled]。RadioGroup用于组合多个ContextMenu.RadioItem。Prop说明类型modelValue组内选中项的值AcceptableValue事件update:modelValue值变化[payload: AcceptableValue]。RadioItem可受控、像单选框一样渲染的条目。Prop说明类型必填value条目的唯一值AcceptableValue是disabled禁用交互boolean否textValuetypeahead 用文本string否事件select同 CheckboxItem 语义。数据属性同 CheckboxItem。ItemIndicator当父级CheckboxItem或RadioItem处于勾选态时渲染。可以直接设置样式也可以作为包裹图标的容器或两者兼用。数据属性[data-state]checked/unchecked/indeterminate。Separator用于在视觉上分隔菜单条目支持as/asChild。Sub包含子菜单的全部部件。支持as/asChild默认渲染为div。SubTrigger打开子菜单的条目必须渲染在ContextMenu.Sub内。支持as/asChild、disabled、textValue等 props语义同 Item。数据属性[data-state]open/closed、[data-highlighted]、[data-disabled]。SubContent子菜单打开时弹出的部件必须渲染在ContextMenu.Sub内。其 Props 与 Content 基本一致含sideOffset、arrowPadding、updatePositionStrategy、alignFlip、sideFlip等定位与碰撞控制事件额外包含entryFocus容器被聚焦时回调与openAutoFocus打开时自动聚焦回调。数据属性与 CSS 变量均与 Content 相同data-state、data-side、data-align以及 5 个--reka-context-menu-*变量。实战示例子菜单With submenus通过ContextMenuSub及其部件组合出多级菜单script setup langts import { ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRoot, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from reka-ui /script template ContextMenuRoot ContextMenuTrigger…/ContextMenuTrigger ContextMenuPortal ContextMenuContent ContextMenuItem…/ContextMenuItem ContextMenuItem…/ContextMenuItem ContextMenuSeparator / ContextMenuSub ContextMenuSubTriggerSub menu →/ContextMenuSubTrigger ContextMenuPortal ContextMenuSubContent ContextMenuItemSub menu item/ContextMenuItem ContextMenuItemSub menu item/ContextMenuItem ContextMenuArrow / /ContextMenuSubContent /ContextMenuPortal /ContextMenuSub ContextMenuSeparator / ContextMenuItem…/ContextMenuItem /ContextMenuContent /ContextMenuPortal /ContextMenuRoot /template注意子菜单内部同样用ContextMenuPortal包裹SubContent并可以放置ContextMenuArrow指向触发项。禁用条目With disabled items通过disabled禁用条目并利用data-disabled属性添加特殊样式template ContextMenuRoot ContextMenuTrigger…/ContextMenuTrigger ContextMenuPortal ContextMenuContent ContextMenuItem classContextMenuItem disabled…/ContextMenuItem ContextMenuItem classContextMenuItem…/ContextMenuItem /ContextMenuContent /ContextMenuPortal /ContextMenuRoot /template/* styles.css */ .ContextMenuItem[data-disabled] { color: gainsboro; }分隔线With separators使用Separator部件在条目之间添加分隔ContextMenuContent ContextMenuItem…/ContextMenuItem ContextMenuSeparator / ContextMenuItem…/ContextMenuItem ContextMenuSeparator / ContextMenuItem…/ContextMenuItem /ContextMenuContent标签With labels使用Label部件为区块添加标签不可通过方向键聚焦ContextMenuContent ContextMenuLabelLabel/ContextMenuLabel ContextMenuItem…/ContextMenuItem ContextMenuItem…/ContextMenuItem ContextMenuItem…/ContextMenuItem /ContextMenuContent勾选项With checkbox items使用CheckboxItem添加可勾选条目配合ItemIndicator渲染勾选图标示例使用iconify/vue的radix-icons:check图标script setup langts import { Icon } from iconify/vue import { ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuItemIndicator, ContextMenuPortal, ContextMenuRoot, ContextMenuSeparator, ContextMenuTrigger, } from reka-ui const checked ref(true) /script template ContextMenuRoot ContextMenuTrigger…/ContextMenuTrigger ContextMenuPortal ContextMenuContent ContextMenuItem…/ContextMenuItem ContextMenuItem…/ContextMenuItem ContextMenuSeparator / ContextMenuCheckboxItem v-modelchecked ContextMenuItemIndicator Icon iconradix-icons:check / /ContextMenuItemIndicator Checkbox item /ContextMenuCheckboxItem /ContextMenuContent /ContextMenuPortal /ContextMenuRoot /templatemodelValue支持true/false/indeterminate三种状态因此ItemIndicator也可以依据data-state为中间态渲染不同的图标。单选组With radio items使用RadioGroup与RadioItem实现互斥单选script setup langts import { Icon } from iconify/vue import { ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuItemIndicator, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuRoot, ContextMenuSeparator, ContextMenuTrigger, } from reka-ui const color ref(blue) /script template ContextMenuRoot ContextMenuTrigger…/ContextMenuTrigger ContextMenuPortal ContextMenuContent ContextMenuRadioGroup v-modelcolor ContextMenuRadioItem valuered ContextMenuItemIndicator Icon iconradix-icons:check / /ContextMenuItemIndicator Red /ContextMenuRadioItem ContextMenuRadioItem valueblue ContextMenuItemIndicator Icon iconradix-icons:check / /ContextMenuItemIndicator Blue /ContextMenuRadioItem ContextMenuRadioItem valuegreen ContextMenuItemIndicator Icon iconradix-icons:check / /ContextMenuItemIndicator Green /ContextMenuRadioItem /ContextMenuRadioGroup /ContextMenuContent /ContextMenuPortal /ContextMenuRoot /templateRadioItem的value为必填且需唯一RadioGroup的modelValue与之联动。复杂条目With complex items可以在Item中放入图片等装饰性元素ContextMenuContent ContextMenuItem img src… Adolfo Hess /ContextMenuItem ContextMenuItem img src… Miyah Myles /ContextMenuItem /ContextMenuContent此时若担心 typeahead 定位不准可给每个Item传入textValue覆盖默认的textContent提取逻辑。约束内容尺寸有时需要让 Content或 SubContent宽度与触发器或子触发器一致或限制高度不超过视口。文档暴露的 CSS 变量正好服务于该场景ContextMenuContent classContextMenuContent…/ContextMenuContent/* styles.css */ .ContextMenuContent { width: var(--reka-context-menu-trigger-width); max-height: var(--reka-context-menu-content-available-height); }其中--reka-context-menu-trigger-width让内容宽度贴合触发器--reka-context-menu-content-available-height则保证内容不会超出视口剩余空间碰撞检测后由库实时计算。动画方案基于 transform-origin 的动画暴露的--reka-context-menu-content-transform-origin会根据side、sideOffset、align、alignOffset及碰撞结果计算缩放原点因此可以直接用它做从计算原点展开的缩放动画/* styles.css */ .ContextMenuContent { transform-origin: var(--reka-context-menu-content-transform-origin); animation: scaleIn 0.5s ease-out; } keyframes scaleIn { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }碰撞感知动画data-side与data-align的值会随碰撞在运行时变化因此可以基于它们编写方向感知的滑动动画——例如从上方弹出时向下滑入从下方弹出时向上滑入/* styles.css */ .ContextMenuContent { animation-duration: 0.6s; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); } .ContextMenuContent[data-sidetop] { animation-name: slideUp; } .ContextMenuContent[data-sidebottom] { animation-name: slideDown; } keyframes slideUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }无障碍与键盘交互ContextMenu使用 roving tabindex 管理菜单项间的焦点移动文档在 API Reference 与 Accessibility 两处均声明此机制。完整的键盘交互如下按键行为Space激活当前聚焦条目Enter激活当前聚焦条目ArrowDown焦点移动到下一个条目ArrowUp焦点移动到上一个条目ArrowRight/ArrowLeft焦点位于ContextMenu.SubTrigger时依据阅读方向打开或关闭子菜单Esc关闭上下文菜单配合Root的dirpropltr/rtl可让方向键行为适配阿拉伯语等从右到左的阅读环境Content的loopprop 则可开启首尾循环导航。源码层面的实现印证从源码看ContextMenu是一个典型的组合封装实现ContextMenuRoot.vue 内部通过provideContextMenuRootContext提供open、onOpenChange、modal、dir、triggerElement、pressOpenDelay上下文并将其最终渲染为通用的 MenuRoot各子部件如ContextMenuContent.vue、ContextMenuItem.vue则消费该上下文并委托给Menu目录下的对应实现。因此ContextMenu天然继承Menu的定位、碰撞、焦点与 dismiss 能力这也是文档中Customize side, alignment, offsets, collision handling、Focus is fully managed、Dismissing and layering behavior is highly customizable等特性能够成立的根本原因。仓库中还提供了完整的交互测试 ContextMenu.test.ts 以及可直接运行参考的演示组件docs/components/demo/ContextMenu 下的 css 与 tailwind 两套实现读者在动手集成前可以先查看这些示例确认预期行为。小结ContextMenu覆盖了右键菜单从触发到弹出定位再到条目交互的完整链路Root控制模态与长按延迟Trigger负责右键/长按捕获Content/SubContent提供丰富的定位、碰撞与事件钩子Item 家族普通/勾选/单选/标签/分隔线/子菜单满足绝大多数菜单结构需求5 个 CSS 变量与data-state/data-side/data-align属性则为样式与动画留出了充足的定制空间。对无障碍有严格要求的场景它开箱即用的 roving tabindex 与完整键盘导航也能直接达标。【免费下载链接】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),仅供参考
返回列表