
radix-vue Presence 组件完全指南用 CSS 动画控制元素的挂载与卸载【免费下载链接】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导读Presence是 radix-vueReka UI 的前身Vue 版无障碍 UI 组件库提供的一个通用工具组件核心能力是带着动画完成元素挂载/卸载当present变为false时它不会像v-if那样立刻把节点从 DOM 中移除而是等退场动画leave animation完整播完再卸载从而让弹窗、下拉菜单、Tooltip 等组件的开合动画得以平滑呈现。读完本文你将掌握Presence的present/forceMount两个 Props、四个生命周期事件enter/after-enter/leave/after-leave的完整用法并理解其底层挂载状态机 CSS Animation 事件监听的实现原理能独立为任何 radix-vue 组件接入 CSS 动画。与 VueTransition的本质区别官方文档用一个 FAQ 点明了二者差异VueTransition面向的是进出过渡transition而Presence接受的是 CSSanimation关键帧动画并直接控制元素的可见性visibility与挂载状态。具体来说Presence通过读取元素的计算样式animation-name来判断退场动画是否在运行动画未结束前将元素扣留在 DOM 中它本身不做视觉过渡只负责挂载 / 暂缓卸载 / 卸载三态调度动画样式完全由你通过 CSS 类或data-state属性自行控制也正因如此Presence可以与原生Transition组合使用见下文与 Vue Transition 组合一节两者职责互不冲突。Presence在 radix-vue 中几乎无处不在——Dialog、Popover、DropdownMenu、Tooltip 等需要开合动画的组件内部都依赖它来延迟卸载它正是这些弹出层组件退出动画不闪断的底层保障。API 参考Presence组件的全部 Props 与 Emits 定义见 Presence.ts从包入口 index.ts 统一对外导出。Props名称类型必填默认值说明presentboolean是—控制子元素挂载 / 卸载的条件语义上类似v-if。为true时挂载为false时先播放退场动画、动画结束后卸载forceMountboolean否false强制元素始终渲染。常用于需要父组件不卸载、但按需控制更深层子组件显隐的场景可通过作用域插槽拿到暴露的present值手动控制Emits事件这四个事件都是派发在真实 DOM 节点上的CustomEvent用于在动画的关键节点给外部提供钩子事件名触发时机类型enter入场动画开始时触发CustomEventafter-enter入场动画结束后触发CustomEventleave退场动画开始时触发CustomEventafter-leave退场动画结束后触发CustomEvent关于事件派发有一个值得注意的实现细节在 usePresence.ts 中事件通过new CustomEvent(name, { bubbles: false, cancelable: false })构造并dispatchEvent到目标节点上且仅在isClient客户端环境下派发——这是因为 Node 18 环境没有CustomEvent对应上游 reka-ui issue #930所以测试与 SSR 场景需要额外处理。快速上手第一个带动画的 Presence官方文档给出的最小示例presence.md如下template Presence :presentisVisible div :data-stateisVisible ? open : closed classdata-[stateopen]:animate-fadeIn>keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } /* 通过>template Presence v-slot{ present } :presentisVisible :force-mounttrue div This content will always be rendered div v-ifpresent This content is hidden /div /div /Presence /template两个关键点作用域插槽暴露presentPresence的默认插槽签名是(opts: { present: boolean }) any见 Presence.tspresent是组件内部当前是否应出现在 DOM 中的实时状态——注意它不等价于props.present在退场动画播放期间unmountSuspended状态它依然为true。用它来做v-if判断可以精确控制内部元素的卸载时机。forceMount与present是或的关系从渲染函数可以看到判断逻辑是if (forceMount.value || present.value || isPresent.value)Presence.ts即只要三者任一为真就渲染。因此forceMount开启后根节点永不卸载退场动画会悬空在已渲染但不可见的内部元素上执行。典型应用包在 VueTransition外面一个非常实用的组合是Transition Presence(force-mount)用原生Transition负责进出过渡样式用Presence负责挂载状态控制。官方 story PresenceTransition.story.vue 展示了这种用法Transition Presence :presentisOpen force-mount divContent/div /Presence /Transition style .v-enter-active, .v-leave-active { transition: opacity 0.5s ease; } .v-enter-from, .v-leave-to { opacity: 0; } /style这里forceMount保证Presence不会主动卸载根节点卸载决策完全交给外层Transition避免两套卸载机制互相打架。原理剖析usePresence 的三态状态机组件本体 Presence.ts 只是薄薄的壳Props 定义、单子元素校验、h()渲染与 ref 绑定真正的动画调度逻辑全部在组合式函数 usePresence.ts 中。理解它你就理解了 Presence 的全部。状态机mounted / unmountSuspended / unmountedconst { state, dispatch } useStateMachine(initialState, { mounted: { UNMOUNT: unmounted, ANIMATION_OUT: unmountSuspended, }, unmountSuspended: { MOUNT: mounted, ANIMATION_END: unmounted, }, unmounted: { MOUNT: mounted, }, })mounted元素在 DOM 中。当present变false且检测到退场动画存在 → 派发ANIMATION_OUT进入unmountSuspended没有动画 → 直接UNMOUNT。unmountSuspended元素仍保留在 DOM 中等待动画结束animationend到达 →ANIMATION_END进入unmounted期间present重新变true→ 立即MOUNT回到mounted实现中途取消退出。unmounted元素已卸载present变true→MOUNT重新挂载。对外暴露的isPresent是computed(() [mounted, unmountSuspended].includes(state.value))usePresence.ts即暂缓卸载期间仍视为存在。如何判断有没有退场动画关键手段是读取计算样式中的animation-name而不是依赖animationstart事件——注释里明确说明了原因animationstart会等到animation-delay结束才触发对立即判断来说太晚了而animationrun事件又不存在usePresence.ts。核心逻辑function getAnimationName(node?: HTMLElement) { return node ? getComputedStyle(node).animationName || none : none }present变化时watch回调中比较prevAnimationName与currentAnimationName不一致 → 认为动画在运行进入unmountSuspended一致或为none→ 直接卸载。同时还会检查display: none元素不可见时动画不会运行需立即卸载。三个原生事件驱动状态流转节点挂载后注册三个监听器usePresence.tsanimationstart→ 记录当前animation-name作为上一次动画供下一次比较animationend→ 用CSS.escape(event.animationName)与当前动画名比对只对当前正在播放的动画派发after-enter/after-leave并ANIMATION_END避免入场动画被animationcancel打断时误判对应注释make sure we only trigger ANIMATION_END for the currently active animationanimationcancel→ 同样走handleAnimationEnd保证动画被取消时也能正确卸载。handleAnimationEnd中还有一个细节退场结束时临时把animationFillMode设为forwards再在setTimeout里复位防止节点卸载前出现闪帧usePresence.ts。单一子元素约束Presence类似v-if的语义决定了它只接受一个有效的子 VNode。源码在 setup 阶段用renderSlotFragments展开插槽片段后校验数量超过一个会直接抛错错误信息还给出了两个修复建议只提供一个子元素或确保第一个子节点是真实元素而非文本/注释节点Presence.ts。多动画与边界情况官方 story PresenceAnimation.story.vue 覆盖了多组典型场景可作进阶参考仅入场动画如classanimate-fadeIn挂载时播放卸载时直接移除仅退场动画如classdata-[stateclosed]:animate-fadeOut挂载立即显示卸载时等待动画入场 退场动画即基础示例形态通过data-state切换多重动画一个元素同时挂多个animation如fadeIn slideUp2组合状态机通过animation-name的完整字符串比对仍然可以正确识别延迟挂载动画Deferred Mount Animation元素挂载后约 150ms 才动态加上动画类story 注释也如实指出内容会短暂闪烁——这是 CSS 动画机制本身的固有限制Presence只能尽力而为。另一个容易踩的坑子元素派发的冒泡动画事件必须被忽略。测试用例Presence.test.ts专门验证了这一点handleAnimationEnd首先检查event.target ! node.value就返回因此孙元素如弹窗里的某个动画子组件冒泡上来的animationend不会干扰Presence对自身动画的判断。测试如何验证这些行为Presence.test.ts 是理解行为契约的最佳文档几个代表性断言默认presentfalse不渲染expect(wrapper.html()).not.toContain(CONTENT)切换后渲染 / 再切换后移除点击按钮改变open后内容出现、再点后消失forceMount常驻无论present如何内容始终在 DOM 中且作用域插槽的data-present属性会随present在false/true间切换带动画内容注入fadeIn/fadeOut关键帧后present切换依然能正确挂载与卸载说明动画场景下状态机工作正常。这些用例覆盖了 Props 的两个核心行为和动画场景下的生命周期也是你在自己的项目里复刻Presence用法时的验收标准。更多参考组件 API 文档presence.md组件实现Presence.ts、usePresence.ts单元测试Presence.test.ts交互式示例 storyPresenceBasic.story.vue、PresenceAnimation.story.vue、PresenceTransition.story.vue官方动画/过渡指南animation.md——涵盖 CSS 关键帧动画与 VueTransition两种接入方式的完整示例是Presence的配套实战手册【免费下载链接】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),仅供参考