
UI组件前端【免费下载链接】ariakitToolkit with accessible components, styles, and examples for your next web app项目地址https://gitcode.com/gh_mirrors/ar/ariakit点击查看免费下载本文基于 Ariakit 官方示例 Selection Popover讲解如何在用户用鼠标选中一段文字时在选区正上方弹出一个带箭头、带操作按钮的 Popover书签 / 编辑 / 分享。读完本文你将掌握 AriakitPopover的虚拟锚点定位 APIgetAnchorRect、可编程的hideOnInteractOutside关闭策略以及用PopoverStore从组件外部事件监听器中控制弹层渲染与打开的完整链路并能复现出类似浏览器原生“划词菜单”的交互效果。示例效果与核心思路Ariakit 文档对该示例的原始描述是“Showing an inline Popover when a text is selected. This example uses thegetAnchorRectprop to position the flyout relative to the selected text.”见 readme。把它拆开示例需要解决三个问题锚点不是一个 DOM 元素Popover 默认锚定在PopoverDisclosure或 provider 上下文中的 anchor 元素上而“选中的文字”没有对应的 DOM 节点。Ariakit 用getAnchorRect解决这个问题——返回一个虚拟锚点的DOMRectPopover 会把它当作getBoundingClientRect的结果参与 floating UI 定位。弹出时机不可控在组件生命周期内划词完成发生在mouseup/selectionchange这类文档级事件上必须从外部拿到 store 来调用popover.render()和popover.setOpen(true)。关闭条件要“语义化”用户点击弹层外部时只有当选区仍然落在目标文本内才关闭弹层避免“点一下就消失、重选又出现”的抖动。三者分别对应getAnchorRect、usePopoverStore 事件监听、以及hideOnInteractOutside接受函数的能力。完整示例代码下面是 examples/popover-selection/index.react.tsx 的完整实现import * as Ariakit from ariakit/react; import { useEffect, useRef } from react; import ./style.css; function hasSelectionWithin(element?: Element | null) { const selection element?.ownerDocument.getSelection(); if (!selection?.rangeCount) return false; const range selection.getRangeAt(0); if (range.collapsed) return false; return !!element?.contains(range.commonAncestorContainer); } export default function Example() { const popoverRef useRefHTMLDivElement(null); const paragraphRef useRefHTMLParagraphElement(null); const popover Ariakit.usePopoverStore(); useEffect(() { const popoverContainer popoverRef.current; const paragraph paragraphRef.current; if (!popoverContainer) return; if (!paragraph) return; const doc paragraph.ownerDocument || document; const onMouseUp () { if (!hasSelectionWithin(paragraph)) return; popover.render(); popover.setOpen(true); }; const onSelect () { if (popoverContainer.contains(doc.activeElement)) return; if (hasSelectionWithin(paragraph)) { return popover.render(); } popover.setOpen(false); }; doc.addEventListener(mouseup, onMouseUp); doc.addEventListener(selectionchange, onSelect); return () { doc.removeEventListener(mouseup, onMouseUp); doc.removeEventListener(selectionchange, onSelect); }; }, [popover]); return ( div Ariakit.PopoverProvider store{popover} placementtop Ariakit.Popover autoFocusOnShow{false} hideOnInteractOutside{() !hasSelectionWithin(paragraphRef.current) } ref{popoverRef} classNamepopover getAnchorRect{() { const selection paragraphRef.current?.ownerDocument.getSelection(); if (!selection?.rangeCount) return null; const range selection.getRangeAt(0); return range.getBoundingClientRect(); }} Ariakit.PopoverArrow size{24} classNamearrow / Ariakit.Button classNamebutton secondaryBookmark/Ariakit.Button Ariakit.Button classNamebutton secondaryEdit/Ariakit.Button Ariakit.Button classNamebutton secondaryShare/Ariakit.Button /Ariakit.Popover /Ariakit.PopoverProvider p ref{paragraphRef} Lorem ipsum dolor, sit amet consectetur adipisicing elit. Odio, sed fuga necessitatibus aliquid expedita atque? Doloremque ea sequi totam laudantium laboriosam repellat quasi commodi omnis aut nulla. Numquam, beatae maxime. /p /div ); }配套的样式见 style.css它通过import复用基础 Popover 示例的卡片、箭头与按钮样式再把.popover从纵向布局改为flex-row、按钮高度收敛为h-8让三个操作按钮横向排成一条工具栏import url(../popover/style.css); .popover { apply flex-row p-1 gap-0; } .button { apply rounded px-2 h-8; }其中被复用的基础样式定义在 examples/popover/style.cssz-50、rounded-lg、边框阴影、focus-visible外框等。关键 API 逐一拆解1. getAnchorRect把选区变成虚拟锚点getAnchorRect的官方签名在 popover.tsx 中定义/** * Function that returns the anchor elements DOMRect. If this is explicitly * passed, it will override the anchor getBoundingClientRect method. */ getAnchorRect?: (anchor: HTMLElement | null) AnchorRect | null;从源码结构看它的作用方式在getAnchorElement中popover.tsxAriakit 构造了一个符合 floating UIvirtual element约定的对象其getBoundingClientRect会优先调用getAnchorRect只有当回调返回空且存在真实 anchor 元素时才回退到元素自身的getBoundingClientRect。因此示例中getAnchorRect返回selection.getRangeAt(0).getBoundingClientRect()Popover 的定位就完全“跟随”选区矩形而不是跟随任何 DOM 节点。几个与定位相关的细节同样来自 popover.tsx该对象随后被交给 floating UI 的computePosition约 L417与autoUpdate约 L501所以选区变化、窗口滚动 / 尺寸变化时弹层会自动重新定位中间件按offset → flip → shift → arrow → size顺序组装约 L393-L414示例里箭头的位置由arrow中间件结合PopoverArrow自动计算位置最终以translate3d写入包装层样式约 L429-L437用户无需覆盖位置样式就能做进入 / 离开动画最终写回 store 的currentPlacement示例在PopoverProvider上设了placementtop所以弹层默认出现在选区上方。getAnchorRect的其他典型用法还包括独立 Popover无 anchor 元素和右键上下文菜单官方 JSDoc 中列出的 live examples 有 standalone popover、context menu 与本示例。2. 事件监听何时 render、何时 setOpenusePopoverStore在 React 中创建的 storepopover-store.ts持有open、rendered、mounted等状态示例利用它把“渲染”和“打开”分两步驱动popover.render()把 Popover 挂载进 DOMrendered true但不一定打开popover.setOpen(true)真正打开触发定位与可选的自动聚焦。onMouseUp中两者都调用是因为mouseup触发时弹层可能尚未挂载需要先render再setOpen而onSelect监听selectionchange只调用render()让 store 根据open状态自行收敛。两处都有两个保护条件值得注意onSelect开头if (popoverContainer.contains(doc.activeElement)) return;焦点已在弹层内部比如鼠标在按钮上拖拽时不做任何处理避免把用户正在使用的弹层关 / 开两个回调都先用hasSelectionWithin(paragraph)判断选区是否真的落在目标段落内并额外要求range.collapsed false防止“单击后产生零宽选区”误触发弹出。hasSelectionWithin的实现只依赖标准 Web APIgetSelection()取第一个 range用range.commonAncestorContainer判断是否仍被目标元素包含。它被同时用在事件监听与hideOnInteractOutside回调里是示例中唯一的“业务判据”。3. hideOnInteractOutside让关闭条件可语义化hideOnInteractOutside继承自 Dialog 层能力。从 use-hide-on-interact-outside.ts 看Ariakit 对取值做了分支function shouldHideOnInteractOutside( hideOnInteractOutside: DialogOptions[hideOnInteractOutside], event: Event, ) { if (typeof hideOnInteractOutside function) { return hideOnInteractOutside(event); } return !!hideOnInteractOutside; }即传布尔值走默认策略传函数则由你自己决定“这次外部交互是否应该关闭弹层”。该检查挂在click、focusin、contextmenu三类全局捕获监听上同一文件中useEventOutside的三处调用。示例传入hideOnInteractOutside{() !hasSelectionWithin(paragraphRef.current)}含义是只要选区还在目标文本里点外部也不关闭返回false 不关闭用户一旦划走选区 / 点空导致选区消失下次外部交互就会触发store.hide()。这与onSelect里“选区消失即setOpen(false)”互相配合从两个方向保证弹层与选区状态一致。4. autoFocusOnShow{false} 与 PopoverArrowautoFocusOnShow默认为true见 popover.tsx 的默认参数。划词菜单场景下焦点刚从文本编辑上下文移出若立刻把焦点抢进弹层会打断用户的下一步选择示例显式关闭。源码中还有一处细节useDialog实际收到的是autoFocusOnShow: positioned autoFocusOnShow约 L642即首次定位完成前不会聚焦防止定位过程中焦点跳变引起滚动跳动。PopoverArrow size{24}注册为 store 的arrowElementarrow中间件在每次定位后把箭头left/top与边贴合样式写入 DOM约 L442-L472并同步--popover-transform-origin供进入动画以箭头尖为原点缩放。示例中.arrow的填充 / 描边色沿用基础样式的[svg]:fill-white等规则examples/popover/style.css。可迁移的扩展点基于上述结构这个示例向几个方向迁移成本很低锚到其他虚拟位置右键坐标菜单可把event.clientX/clientY包成{ x, y, width: 0, height: 0 }返回给getAnchorRect输入框内某个词的高亮位置同理官方combobox-textarea示例也是getAnchorRect的典型用例。约束弹层尺寸PopoverOptions提供sameWidth弹层与锚点同宽并暴露--popover-anchor-widthCSS 变量、fitViewport按视口剩余空间限制maxWidth/maxHeight、gutter与锚点间距默认 0、flip/slide/overflowPadding溢出翻转与边距overflowPadding默认 8px等选项完整签名见 popover.tsx。选区通常很窄若希望菜单有最小宽度可在样式中直接使用包装层暴露的 CSS 变量而非依赖sameWidth。关闭策略组合hideOnInteractOutside的函数形式只影响“外部交互”这一条关闭路径Escape、失焦等仍由 Dialog 层统一处理两者可以分别定制。多文档 / iframe示例全程通过paragraph.ownerDocument获取getSelection与事件目标而不是硬编码document这在弹层被 portal 到不同文档、或页面本身在 iframe 中时仍然成立。小结Selection Popover 示例展示的是 Ariakit Popover 定位体系中最灵活的一块能力通过getAnchorRect把任意可计算矩形文本选区、鼠标坐标、词高亮范围作为虚拟锚点配合usePopoverStore从文档级事件监听器中render()/setOpen()再用hideOnInteractOutside的函数形式把“何时可以关闭”与选区状态绑定。相关文档可继续参阅 Popover 组件说明、组件实现 popover.tsx 与外部交互关闭逻辑 use-hide-on-interact-outside.ts。赞分享UI组件前端【免费下载链接】ariakitToolkit with accessible components, styles, and examples for your next web app项目地址https://gitcode.com/gh_mirrors/ar/ariakit点击查看免费下载相关推荐Ariakit Lazy Popover用 React.lazy 与 useTransition 实现按需加载的 Popover 组件Ariakit Lazy Popover用 React.lazy 与 useTransition 实现按需加载的 Popover 组件 本文基于 AriakiUI组件前端Element UI弹出层组件Popover、Tooltip、PopconfirmElement UI弹出层组件Popover、Tooltip、Popconfirm Element UI作为基于Vue.js 2.0的Web UI工具包提供前端UI组件设计系统Ariakit Popover、Tooltip、Hovercard 一文讲透3种悬浮提示到底怎么选Ariakit Popover、Tooltip、Hovercard 一文讲透3种悬浮提示到底怎么选 在 React 可访问性组件库 Ariakit 中 PoUI组件前端上一篇Polar 前端性能优化实战用 next/dynamic 延迟加载非关键第三方库Defer Non-Critical Third-Party Libraries下一篇微信支付公众号支付(JS API)完整教程使用weixin-pay实现H5支付创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考