啥是vue router里的transition?和普通transition有啥区别?
p不少刚接触Vue开发的朋友,在做页面切换动画时总会犯难:vue - router和transition结合到底咋玩?为啥别人的页面切换丝滑又好看,自己折腾半天要么没效果,要么动画逻辑乱成一团?这篇文章就用问答的形式,把vue - router transition的用法、套路、踩坑点一次性讲透,从基础到进阶全覆盖~
先搞明白基础概念!Vue里的<transition>
是专门处理单元素/组件过渡的内置组件,能在元素插入、删除时加动画,但vue - router场景下,<transition>
是和<router - view>
结合,给路由切换做动画——比如从首页跳转到关于页时,让离开的页面慢慢消失,进入的页面缓缓浮现。
和普通<transition>
区别在哪?普通<transition>
管的是组件内部元素的过渡(比如按钮显示隐藏);而和vue - router结合时,管的是不同页面(路由组件)之间的切换过渡,核心逻辑还是利用<transition>
的enter(进入)、leave(离开)两个阶段,但载体从组件内元素变成了整个页面的<router - view>
~
最基础的路由切换动画咋实现?
想做个最简单的淡入淡出?三步搞定:
第一步:用<transition>
把<router - view>
包起来,模板里这么写:
<template> <transition name="fade"> <router - view></router - view> </transition> </template>
这里的name="fade"
很关键,它会给CSS过渡类自动加前缀(比如fade - enter - from
)。
第二步:写CSS过渡样式,要覆盖进入和离开的四个阶段:
fade - enter - from
:进入前的初始状态(比如透明度0)fade - enter - active
:进入过程的过渡规则(比如过渡时长、曲线)fade - leave - to
:离开后的最终状态(比如透明度0)fade - leave - active
:离开过程的过渡规则
对应的CSS代码:
.fade - enter - from, .fade - leave - to { opacity: 0; } .fade - enter - active, .fade - leave - active { transition: opacity 0.5s ease; }
第三步:保存刷新,路由切换时就能看到淡入淡出效果啦!原理是:当路由切换时,离开的页面(旧<router - view>
里的组件)会触发leave阶段(从opacity:1
变到opacity:0
),进入的页面(新组件)触发enter阶段(从opacity:0
变到opacity:1
),过渡过程由transition
属性控制时长和曲线~
想给不同路由配不同动画,咋搞?
产品说“首页跳列表页滑入,列表跳详情页缩放”,得给不同路由定制动画咋办?用路由元信息(meta)+ 动态name就能实现!
步骤拆解:
-
给路由配置加
meta.animation
,比如路由文件里:const routes = [ { path: '/home', component: Home, meta: { animation: 'slide - left' } // 首页用左滑动画 }, { path: '/list', component: List, meta: { animation: 'scale - in' } // 列表页用缩放动画 } ]
-
让
<transition>
的name动态变化,在包裹<router - view>
的组件里,用计算属性拿当前路由的meta.animation
:<template> <transition :name="currentAnimation"> <router - view></router - view> </transition> </template>
- 写对应CSS,比如
slide - left
的左滑:.slide - left - enter - from { transform: translateX(100%); /* 从右边进来 */ } .slide - left - enter - active { transition: transform 0.5s; } .slide - left - leave - to { transform: translateX(-100%); /* 向左滑出 */ } .slide - left - leave - active { transition: transform 0.5s; }
.scale - in - enter - from { transform: scale(0.8); opacity: 0; } .scale - in - enter - active { transition: all 0.5s; } / 离开动画同理,根据需求加scale - out的leave样式 /
这样不同路由切换时,`<transition>`的name会自动换成路由`meta`里的动画名,加载对应CSS,实现差异化动画~
### 页面返回时动画反过来,咋实现?
比如从详情页返回列表页,希望动画是“右滑回退”,而不是和进入时一样的左滑,这需要判断**路由是前进还是后退**,再动态改动画方向。
核心思路:通过路由的`matched.length`判断层级(比如从子路由回父路由属于后退),或者记录路由切换的方向,具体步骤:
1. 记录路由切换方向(`isBack`),在组件里用`watch`监听`$route`变化:
```js
export default {
data() {
return {
isBack: false
}
},
watch: {
$route(to, from) {
// from的匹配层级比to深 → 说明是后退(比如从 /list/1 回 /list)
this.isBack = from.matched.length > to.matched.length
}
}
}
-
动态切换
<transition>
的name,比如前进用slide - left
,后退用slide - right
:<transition :name="isBack ? 'slide - right' : 'slide - left'"> <router - view></router - view> </transition>
-
写反向动画的CSS。
slide - right
的逻辑和slide - left
相反:.slide - right - enter - from { transform: translateX(-100%); /* 从左边进来(后退时的进入) */ } .slide - right - enter - active { transition: transform 0.5s; } .slide - right - leave - to { transform: translateX(100%); /* 向右滑出(后退时的离开) */ } .slide - right - leave - active { transition: transform 0.5s; }
这样用户点击返回按钮(或$router.go(-1)
)时,isBack
变为true,动画自动切换成反向滑动,体验更自然~
嵌套路由的动画咋处理?
项目里用了嵌套路由(比如首页布局里嵌套了子路由),动画咋不冲突?关键是给每个层级的<router - view>
单独加<transition>
。
举个嵌套结构:
- 父路由:
Layout
(包含顶部栏、侧边栏,中间是<router - view>
显示子路由) - 子路由:
Home
、About
(对应Layout
中间区域的内容)
处理方式:
- 父路由组件(
Layout
)的<router - view>
加过渡,负责父级布局切换的动画(比如整体淡入):<template> <div class="layout"> <header>顶部栏</header> <transition name="parent - fade"> <router - view></router - view> <!-- 这里渲染子路由(Home/About) --> </transition> </div> </template>
- 子路由组件内部如果还有嵌套(比如
Home
里又有<router - view>
显示孙子路由),同样包裹<transition>
,比如Home
组件:<template> <div class="home"> <h1>首页</h1> <transition name="child - slide"> <router - view></router - view> <!-- 渲染孙子路由(比如Tab1/Tab2) --> </transition> </div> </template>
这样每个层级的路由切换动画独立控制:父级用fade,子级用slide,孙子级还能加其他动画,互不干扰~
动画和路由守卫结合,能玩出啥花样?
路由守卫(比如beforeEnter
、beforeRouteEnter
)能控制页面跳转的逻辑,和动画结合可以做更灵活的交互,举两个实用场景:
场景1:数据加载完再执行进入动画
避免页面还没加载完就播放动画,导致闪烁。
做法:在路由守卫里设置加载状态,等数据加载完再渲染<router - view>
。
路由配置(beforeEnter
):
{ path: '/detail/:id', component: Detail, beforeEnter: (to, from, next) => { // 模拟请求数据 fetchDetail(to.params.id).then(data => { next(vm => { vm.isLoaded = true; // 传给Detail组件,标记数据加载完成 }); }); } }
Detail
组件里控制<transition>
的显示:
<template> <transition name="fade" v - if="isLoaded"> <router - view></router - view> </transition> </template> <script> export default { data() { return { isLoaded: false } } } </script>
这样数据加载完成后,<transition>
才会渲染,动画更流畅~
场景2:根据用户权限换动画
比如VIP用户给个炫酷的缩放动画,普通用户用默认淡入。
做法:在beforeRouteEnter
里判断权限,给组件传动画名:
路由配置:
{ path: '/vip', component: VipPage, beforeEnter: (to, from, next) => { const isVip = checkVip(); // 自定义权限判断函数 next(vm => { vm.animationName = isVip ? 'vip - scale' : 'normal - fade'; }); } }
VipPage
组件里动态绑定name:
<transition :name="animationName"> <router - view></router - view> </transition>
再配合对应的CSS,就能实现权限差异化动画~
用JS钩子实现动画,和CSS动画有啥不同?
如果觉得CSS动画不够灵活(比如要做复杂的序列动画、结合第三方库),可以用<transition>
的JS钩子(@enter
、@leave
等)。
先看区别:
- CSS动画:用样式声明过渡,适合简单的
opacity
、transform
变化,写起来简洁。 - JS动画:用JS代码控制每帧变化,适合复杂逻辑(比如链式动画、和GSAP/Anime.js等库结合)。
举个用GSAP库做动画的例子:
-
给
<transition>
绑定JS钩子:<transition @enter="enterHook" @leave="leaveHook" > <router - view></router - view> </transition>
-
在
methods
里写钩子函数(注意:必须调用done
回调,否则过渡不会结束):import gsap from 'gsap';
export default { methods: { enterHook(el, done) { // el是要进入的页面元素,done是完成回调 gsap.fromTo(el, { opacity: 0, y: 50 }, // 初始状态 { opacity: 1, y: 0, duration: 0.5, onComplete: done // 动画完成后调用done,让过渡结束 } ); }, leaveHook(el, done) { gsap.to(el, { opacity: 0, y: - 50, duration: 0.5, onComplete: done }); } } }
优势在哪?比如要做“先放大再位移”的复杂动画,CSS很难用一条`transition`声明实现,但JS钩子可以用GSAP的`timeline`精确控制每一步:
```js
enterHook(el, done) {
const tl = gsap.timeline();
tl.from(el, { scale: 0.5, opacity: 0 })
.to(el, { x: 50, duration: 0.3 })
.to(el, { x: 0, duration: 0.3 })
.eventCallback('onComplete', done); // 所有动画完成后调用done
}
所以JS钩子适合需要精细化控制动画流程的场景,而CSS适合快速实现基础过渡~
常见踩坑点有哪些?咋解决?
折腾动画时总会遇到奇奇怪怪的问题,总结几个高频坑和解法:
坑1:动画完全没效果
排查点:
<transition>
是否正确包裹<router - view>
?(别写成<router - view><transition></transition></router - view>
,顺序反了!)name
属性和CSS类名是否对应?(比如name
是slide
,CSS得是slide - enter - from
,不能写成fade - enter - from
)- 元素是否被
display:none
等样式隐藏?(transition
对display
切换支持不好,尽量用opacity
、transform
做过渡)
坑2:进入和离开动画同时执行,页面混乱
原因:<transition>
默认mode是in - out
(先进入再离开)或out - in
(先离开再进入)?不,默认是同时执行!所以两个动画会重叠。
解法:给<transition>
加mode="out - in"
,让离开动画执行完,再执行进入动画:
<transition name="fade" mode="out - in"> <router - view></router - view> </transition>
坑3:嵌套路由动画互相干扰
比如父路由和子路由的动画冲突,导致样式错乱。
解法:给每个<router - view>
的<transition>
设置不同的name
,或者用scoped CSS
区分层级,比如父级用parent - fade
,子级用child - slide
,CSS类名不会重复~
坑4:动态路由参数变化,动画不触发
比如从/user/1
跳到/user/2
,路由参数变了但组件复用,<transition>
不会触发过渡。
解法:给<router - view>
加key
,强制组件重新渲染:
<transition name="fade"> <router - view :key="$route.fullPath"></router - view> </transition>
这样每次路由变化(哪怕只是参数),key
都会变,触发enter/leave
动画~
坑5:动画执行一半,路由已经切换
比如离开动画还没做完,新页面已经进来了。
解法:确保leave动画的duration
和JS钩子的done
回调同步,如果用CSS,leave - active
的transition
时长要和动画逻辑匹配;用JS钩子必须调用done
,保证动画完成后再切换~
看完这些问题,再面对路由切换动画是不是有思路多了?从基础淡入淡出,到不同路由差异化动画、返回反向动画、嵌套路由处理,再到和路由守卫、JS钩子结合,核心是利用<transition>
的enter/leave
阶段,结合vue - router的路由变化特性(meta
、matched
、路由方向等)。
记住几个关键:
- 包裹
<router - view>
要用<transition>
,name
对应CSS类名; - 动态动画靠路由
meta
和计算属性; - 前进后退判断用
matched.length
或路由方向; - 嵌套路由要给每个层级的
<router - view>
单独加过渡; - 踩坑时先检查结构、
name
、mode
、key
这些细节~
现在可以动手试试,给项目里的页面切换加上丝滑动画,让用户体验起飞~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。