airi 项目 Vue 3 列表动画实践:TransitionGroup 组件最佳实践与源码级实现解析
airi 项目 Vue 3 列表动画实践TransitionGroup 组件最佳实践与源码级实现解析【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读TransitionGroup是 Vue 3 内置的列表过渡组件专门用于处理一组子元素「进入、离开、移动」三类动画。本文以 component-transition-group.md 最佳实践指南为骨架逐条讲解其使用边界、key规则、tag包装、mode禁用约束与基于 JavaScript 钩子的交错stagger动画并结合 airi 仓库中 double-check-button.vue 与 beat-sync.vue 的真实实现帮助你在自己的 Vue 3 应用中写出稳定、流畅、可维护的列表过渡。读完你既能避开最常见的踩坑点也能看到生产级列表动画的落地写法。适用范围什么是 TransitionGroup以及何时使用它Vue 3 提供了两套内置过渡原语选择依据非常明确Transition针对单个元素/组件的进入与离开enter/leave可在单元素上应用modeout-in/modein-out做进出时序编排TransitionGroup针对v-for渲染的一组列表项除了进入、离开外还额外支持移动move动画——当列表增删、排序导致兄弟节点相对位置变化时Vue 会用 FLIP 思路平滑过渡。实践指南给出一份直接可执行的任务清单是使用TransitionGroup时的默认检查项只在列表与重复项场景使用TransitionGroup为每个直接子元素提供唯一且稳定的key需要语义化或布局类容器时通过tag指定包装元素不要使用modeprop它不被支持交错stagger效果请用 JavaScript 钩子实现。airi 仓库中每个 Vue 应用都遵循 Vue 3 Composition API script setup TypeScript 规范见 vue-best-practices SKILL.md而技能库在“动画相关特性”一节也将TransitionGroup定位为“animated list mutations列表变更动画”的唯一内置选项与 component-transition.md单元素过渡、class-based / state-driven 动画技术互为补充。换句话说遇到“列表项会增删或重排”的需求时首选就是TransitionGroup。只为列表使用 TransitionGroupTransitionGroup是为重复渲染的列表项设计的不应把毫无关系的固定兄弟组件塞进它。此时即使写了name也不会得到预期动画反而引入不必要的层级与约束。错误示范把两个固定组件当作过渡子项template TransitionGroup namefade ComponentA / ComponentB / /TransitionGroup /template正确示范tagul决定真实包装标签v-for列表作为子项template TransitionGroup namelist tagul li v-foritem in items :keyitem.id {{ item.name }} /li /TransitionGroup /template关于tag属性需要补两点实现细节在 Vue 3 中tag已不是必填项——不传时TransitionGroup不会渲染多余的包裹元素直接输出子节点列表但当需要语义化标签ul/ol/table或需要一个确定布局的容器如div flex/grid时显式传入tag是标准做法上述真实实现也都传入了tagdiv。传入的tag元素上可以继续绑定 class/style 与普通 HTML 属性Vue 会统一挂载到该包装元素上attribute 继承规则仍然适用。仓库中的 double-check-button.vue 是一个“固定少数子项”的实战变体它用TransitionGroup namedouble-check-slide tagdiv包裹两个带显式key的按钮keycancel与keyprimary配合v-ifconfirming让取消按钮在确认模式下出现/消失从而触发主按钮被“挤开”的移动动画。这说明TransitionGroup不必局限于超长列表两个带 key 的兄弟元素构成的小型动态集合同样适用——关键在于子项必须是被key标识、可能进入或离开视图的成员而不是互不相干的静态组件。始终提供稳定且唯一的 keykey是TransitionGroup的硬性要求。没有稳定的 keyVue 无法在多次渲染间追踪每个元素的身份位置无法精确对应移动动画自然失效增删元素时还会发生复用错乱。错误示范用数组下标作 keytemplate TransitionGroup namelist tagul li v-for(item, index) in items :keyindex {{ item.name }} /li /TransitionGroup /template正确示范用业务主键作 keytemplate TransitionGroup namelist tagul li v-foritem in items :keyitem.id {{ item.name }} /li /TransitionGroup /template为什么下标 key 是灾难以 index 为 key 时元素身份跟着“位置”走而非跟着“数据”走。插入或删除头部元素会让后续所有元素的身份整体平移Vue 会认为它们是被替换而非移动导致 DOM 复用、过渡动画全部错位。选择 key 时应遵守唯一同一次渲染内互不重复稳定跨多次渲染保持不变与业务实体绑定如数据库 id、自增序号不被复用删除后不再出现避免使用“临时自增计数器”这种会增长的 key会造成数组收缩时 key 错位。这一条与 Vue 通用列表渲染规范一致也是仓库 vue-best-practices 中“模板安全list rendering”要求的一部分。不要在 TransitionGroup 上使用 modemodeout-in/modein-out只属于Transition它用于在同一时刻只能渲染一个元素、需要控制“旧元素离开后再进入新元素”的场景。而TransitionGroup的子元素是同时存在的根本没有“替换单个元素”的语义因此不提供mode——传了会被静默忽略且并不会产生任何进/出排序效果。错误示范对组施加 mode无效且产生误解template TransitionGroup namelist tagdiv modeout-in div v-foritem in items :keyitem.id{{ item.name }}/div /TransitionGroup /template正确示范单元素切换的进出编排请用Transition并通过:key强制视图重建template Transition namefade modeout-in component :iscurrentView :keycurrentView / /Transition /template实现依据mode之所以能起作用是因为Transition内部维护“旧元素销毁 / 新元素挂载”两个阶段而TransitionGroup只关心同一集合内成员的增删移渲染层并不存在“二选一”的时刻。如果你观察仓库中 double-check-button.vue 的“按钮互换”会发现它并未依赖mode而是用v-if 稳定 key 让两个按钮在集合中增删再借助移动过渡与“离场元素脱离布局流”的 CSS 技巧见后文“move 过渡与离场脱离布局流”完成平滑换位——这正是 group 语义下实现进出编排的正确姿势。用 JavaScript 钩子实现交错stagger动画当需要“瀑布式”逐条入场时TransitionGroup的 CSS class 方案不够灵活。最佳实践指南给出的标准做法是关闭 CSS 控制:cssfalse在before-enter/enter钩子中读取子项携带的索引为每个元素计算不同的延迟。原文档提供的完整示例template TransitionGroup tagul :cssfalse before-enteronBeforeEnter enteronEnter li v-for(item, index) in items :keyitem.id :data-indexindex {{ item.name }} /li /TransitionGroup /template script setup function onBeforeEnter(el) { el.style.opacity 0 el.style.transform translateY(12px) } function onEnter(el, done) { const delay Number(el.dataset.index) * 80 setTimeout(() { el.style.transition all 0.25s ease el.style.opacity 1 el.style.transform translateY(0) setTimeout(done, 250) }, delay) } /script逐行拆解这套写法背后的契约:cssfalse告知 Vue 不再依赖 CSS 类判断过渡何时结束enter钩子必须显式调用done()来声明动画完成否则该元素会被认为永远处于过渡中:data-indexindex把索引以 data 属性挂在元素上钩子通过el.dataset.index读取——这是“模板只负责声明、逻辑负责计算延迟”的干净解耦onBeforeEnter设置初始状态透明、向下偏移onEnter按index * 80ms递增延迟后播放到目标态并在自身动画结束后setTimeout(done, 250)通知 Vue 收尾为了视觉自然延迟只应施加于入场路径若同时需要离场交错可继续在leave钩子中做反向处理。airi 仓库中 beat-sync.vue 的“节拍可视化涟漪”模块正是这一模式的工程化版本。它使用:cssfalse只监听enteronRippleEnter为每个涟漪圆点渲染div v-forbeat in beatsHistory :keybeat.id :data-beat-idbeat.id /TransitionGroup tagdiv ... :cssfalse enteronRippleEnter div v-forbeat in beatsHistory :keybeat.id :data-beat-idbeat.id ... / /TransitionGroup对应的 onRippleEnter 从元素上取回业务标识el.dataset.beatId用基于时间线的动画库让圆点从scale: 0, opacity: 1扩散到scale: 1, opacity: 0在onComplete回调里找到对应项并从响应式数组beatsHistory中splice移除最后调用done()完成整个过渡周期function onRippleEnter(el: Element, done: () void) { const beatId (el as HTMLElement).dataset.beatId createTimeline() .set(el, { opacity: 1, scale: 0 }) .add(el, { opacity: 0, scale: 1, duration: 2000, ease: out(5), onComplete: () { if (!beatId) return const idx beatsHistory.value.findIndex(b b.id beatId) if (idx 0) beatsHistory.value.splice(idx, 1) done() }, }) }这个实现值得学习的两点设计数据驱动生命周期动画结束后不是等待外部定时清空而是在done()之前同步从数组中移除该项——元素从 v-for 数据中被删除DOM 才真正退场动画与数据状态天然一致dataset 传参与文档示例的:data-index思路相同把渲染期才可知的身份id/index通过 data 属性传入 JS 钩子避免在闭包里捕获过期索引导致错删。move 过渡与离场元素脱离布局流进入/离开之外TransitionGroup的独有能力是移动动画当列表重排或某个元素离开导致其他成员位移时Vue 会为每个“换了位置”的元素加上name-move类你可以用它声明 transform 过渡实现 FLIP 般的平滑滑动。从 double-check-button.vue 的样式可以看到一个关键组合.double-check-slide-move { transition: transform 180ms ease; } .double-check-slide-enter-active, .double-check-slide-leave-active { transition: opacity 160ms ease, transform 160ms ease; } /* 让离场按钮脱离 flex 布局流主按钮的位移才能被 TransitionGroup 捕获为 move 动画 */ .double-check-slide-leave-active { position: absolute; } .double-check-slide-enter-from, .double-check-slide-leave-to { opacity: 0; transform: translateX(-10px); }其中.double-check-slide-leave-active { position: absolute }是让 move 动画生效的经典技巧默认情况下正在“离开”的元素仍占据文档流空间其余兄弟节点不会真正位移FLIP 也就无从谈起把它设为absolute后离场元素被抽出布局流主按钮的重新排布才成为一次可观测的“移动”配合-move过渡产生顺滑的让位效果。仓库注释也明确说明了这一点“Removing the cancel button from flex flow lets TransitionGroup animate the primary buttons layout shift.”因此一个完整的TransitionGroup过渡通常需要四段 CSS 声明CSS 类作用触发时机name-enter-from/name-enter-active/name-enter-to元素进入动画新增子项时name-leave-from/name-leave-active/name-leave-to元素离开动画删除子项时name-move位置迁移动画一般只写 transition兄弟节点因增删/重排而位移时-leave-active { position: absolute }将离场元素移出布局流使 move 动画可见需要让位动画时常见误区速查与相关参考把本文所有约束收敛为一张自检表可作为评审列表动画代码时的 check-list是否把固定、无关的兄弟组件放进了TransitionGroup——应只包裹会增删移的集合子项每个直接子项是否有keykey是否绑定业务唯一标识而非 index——下标 key 会破坏移动追踪是否给TransitionGroup传了mode——它只适用于Transition需要列表之外的单元素进出编排——改用Transition modeout-in并配合:key切换视图逐条入场是否有延迟控制——用:cssfalse JS 钩子 dataset 索引/标识删除后其余项是否“瞬移”而非滑动——检查是否有name-move过渡以及离场元素是否需要position: absolute脱离布局流JS 钩子模式下是否每个分支都调用了done()——漏调会导致元素悬挂在过渡态。想继续深入相邻主题仓库内可直接阅读以下资源技能库动画决策.agents/skills/vue-best-practices/SKILL.md其中明确列出动画四选一的选型流程单元素过渡配套指南component-transition.md非进出场动画class-based 与 state-driven 两类技术的参考文档见 references 目录生产级实现样例double-check-button.vue多子项 move 让位与 beat-sync.vue:cssfalse JS 钩子动画循环注意区分命名陷阱仓库 ui-transitions 包 中导出的StageTransitionGroup是整套页面级路由转场编排器配合 vue-router 的beforeEach与生命周期钩子系统运行并非 Vue 内置TransitionGroup的封装——当你在依赖中看到以 Group 命名的组件时务必先确认它到底是“列表过渡”还是别的过渡编排避免误用。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →