{T}

主体内容区过渡动画与可配置页面切换效果

概述

这一节实现主体内容区统一的页面切换动画,并把动画做成可在主题设置里动态选择的全局配置项。关键技术落点是:页面级动画应放在布局层的 RouterView 插槽统一处理,用 mode="out-in" 避免新旧页面同时渲染导致的闪烁,<Transition>name 只是 CSS 类前缀(是否生效取决于是否定义了对应过渡类),动态组件上的 key 决定实例是否复用。动画资产应收口到 transition.scss,并与布局容器的稳定边界配合。

这一节提醒一个常见误区:动画看起来是「视觉小事」,但一旦要做成可配置、可持久化、全应用统一的全局项,它就从局部点缀升级成需要治理的视觉资产。治理得好,主题面板改一项全站生效;治理不好,每个页面各自一套时长与命名,最终无法维护。

学习目标

  • <RouterView v-slot> + <Transition> 在布局层统一接管页面切换动画。
  • mode="out-in" 解决切换闪烁,理解它与 in-out 的差异。
  • 理解 name 仅是 CSS 类名前缀,fadefade-transition 并非 Vue 内置同义词。
  • 把切换动画做成 settings 全局配置项,统一收口“配置值、类名前缀、默认值”。
  • key(path / fullPath)定义页面实例边界,并注意与 KeepAlive 缓存粒度联动。
  • 把「有哪些动画」当成单一事实来源管理,避免可选项、默认值、SCSS 定义三处脱节。

一、页面级动画应放在布局层统一处理

页面切换动画不是写在某一个业务组件里,而是放在布局层。在 Vue Router 场景里真正变化的是 RouterView 渲染出的动态组件,所以正确入口是通过插槽取到当前路由对应组件,再交给 <Transition> 包裹,由 Vue 接管进入和离开时机。这样布局层可统一控制所有页面的切换体验,不需要每个页面各写一套。

Vue SFC
<RouterView v-slot="{ Component, route }">
  <Transition :name="transitionName" mode="out-in">
    <component :is="Component" :key="route.path" />
  </Transition>
</RouterView>

路由过渡的入口应放在布局层而非页面内部;RouterView 插槽同时也是页面动画、缓存、复用控制的关键入口;后续叠加 KeepAlive 也通常在这一层组合。

二、mode="out-in" 解决切换闪烁

“有过渡但会闪烁”的根因是默认进入和离开同时开始。当旧页面还没完全离场、新页面已开始入场,两者共享布局空间就容易出现内容重叠、闪一下、视觉抖动。Vue 官方为此提供 mode="out-in":让旧页面先执行离开动画,结束后再插入新页面。

Vue SFC
<Transition name="fade" mode="out-in">
  <component :is="Component" />
</Transition>

out-in 适合大多数后台页面切换;不设 mode 时新旧节点可能并存,需额外处理布局定位。in-out 也存在,但后台业务里远不如 out-in 常见。

三、name 只是 CSS 类前缀,不是动画同义词

fadefade-transition 之所以都能工作,不是 Vue 把它们当成同一类动画——name 的作用只是决定 CSS 过渡类的前缀。name="fade" 匹配 .fade-enter-active 等,name="fade-transition" 匹配 .fade-transition-enter-active 等。所以二者生效只能说明项目里两套类名都被定义过,或用了统一 SCSS 混入批量生成。切换 name 但没有对应关系类时,动画不报错只是不生效;新项目建议统一命名规则,避免同时维护语义重复的前缀。

四、过渡类集中到 transition.scss 做动画资产收口

当项目从简单 fade 扩展到 scaleslide-xslide-y 多套效果时,最容易失控的是样式分散。更合理做法是把过渡类统一集中到 transition.scss,重复规则交给 SCSS mixin,布局层只负责切换 name。这样页面切换动画就像主题色、间距变量一样成为可维护的视觉资产。动画越多越需要统一命名和时长;若后续引入 Animate.css 或 GSAP,也应先明确哪些是页面级动画、哪些是局部交互动效。

scss
@mixin transition-base($duration: 0.24s, $timing: ease) {
  transition: all $duration $timing;
}

.scale-transition-enter-active,
.scale-transition-leave-active {
  @include transition-base(0.24s, ease);
}

.scale-transition-enter-from,
.scale-transition-leave-to {
  opacity: 0;
  transform: scale(0.96);
}

五、把切换动画做成全局可配置项

把切换动画做成下拉选项,意义在于它从布局层写死的内部实现,变成了具备可视化切换、可持久化、全应用统一生效的全局配置项。这也是组件库与普通页面代码的差别之一:不仅功能可用,还要可配置、可管理。

ts
export const TRANSITION_OPTIONS = [
  "fade", "scale-transition", "slide-x-transition", "slide-y-transition"
] as const

动画名若已是 kebab-case 无需额外转换;若持久化主题设置,应保证默认值与可选项一致;主题面板改的是“配置源”,真正渲染动画的地方仍是布局层。

六、toKebabCase 兼容代码命名与 CSS 命名差异

当代码里用 ScaleTransition 这类 PascalCase,而 SCSS 前缀用 kebab-case 时,可用 toKebabCase 在布局层转换。它适合历史代码大量使用 PascalCase、短期不想整体重构的场景。但从长期维护看,如果项目允许重构,直接把存储值设计成最终 CSS 前缀通常更简单。若保留转换函数,要确保默认值、枚举值、SCSS 前缀三者一致——这类小工具一旦转换规则变动,动画会整体失效。

七、key 定义页面实例边界,影响动画触发与缓存

key 的本质不是随便写个值,而是定义动态组件实例的身份边界。没有明确稳定的 key 时,Vue 会尽量复用“看起来相同”的组件,可能导致动画不触发、内容切了但实例被复用、同路由不同参数切换异常。

Vue SFC
<component :is="Component" :key="route.fullPath" />

route.path 适合同路径不依赖查询参数的场景;route.fullPath 能覆盖 query 和 params 变化,但会让缓存粒度更细。若页面同时用了 KeepAlivekey 设计要和缓存策略一起考虑,否则缓存命中率会下降。

八、动画要配合稳定的布局边界

很多开发者只盯着 Transition,却忽略容器本身不稳定时动画仍会抖:高度塌陷、溢出裁切异常、新旧页面同时占位。主体内容区要和动画一起设计容器样式——通常通过 flex: 1min-height: 0overflow: hidden 保持布局稳定。动画期间若发生滚动条抖动,通常要检查外层容器 overflow。后台系统动画要克制,优先保证可读性和响应速度。

九、动画配置项要和主题设置源保持同源

下拉切换动画时,可选项 TRANSITION_OPTIONS、默认值、以及 transition.scss 里实际定义的过渡类必须来自同一份来源。如果某天往 transition.scss 加了新效果却忘了加进可选项,用户选不到;反过来选项里写了但 SCSS 没定义,切过去只是不生效。维护时把「有哪些动画」当成一份单一事实来源管理,而不是分散在三个地方各写一遍,能显著减少「配置里能看到、实际没效果」的尴尬。


常见问题

问题原因解决方案
切换时闪一下或重叠新旧页面同时进入离开<Transition>mode="out-in"
选了名称但无效果name 与实际 CSS 类前缀不匹配检查 transition.scss 是否有对应 *-enter-*/*-leave-*
路由变了动画没触发动态组件被 Vue 复用<component> 设稳定 key(path / fullPath)
同路由不同参数不刷新key 只绑组件类型过于稳定需参数变化也触发时改用 route.fullPath
配置切换后空值或样式异常默认值与可选项不一致给 store 提供合法默认值,选项与过渡类同源
KeepAlive 后动画/缓存异常key 设计与缓存粒度冲突明确动画触发粒度与缓存命中粒度,必要时分开设计
切换动画只在某页偶发失效内容与布局容器不稳定配合 flex:1 / min-height:0 / overflow:hidden 稳定容器

延伸阅读