Code前端首页关于Code前端联系我们

啥是vue router里的transition?和普通transition有啥区别?

terry 12小时前 阅读数 13 #Vue

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就能实现!

步骤拆解:

  1. 给路由配置加meta.animation,比如路由文件里:

    const routes = [
    {
     path: '/home',
     component: Home,
     meta: { animation: 'slide - left' } // 首页用左滑动画
    },
    {
     path: '/list',
     component: List,
     meta: { animation: 'scale - in' } // 列表页用缩放动画
    }
    ]
  2. <transition>的name动态变化,在包裹<router - view>的组件里,用计算属性拿当前路由的meta.animation

    <template>
    <transition :name="currentAnimation">
     <router - view></router - view>
    </transition>
    </template>
```
  1. 写对应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
    }
  }
}
  1. 动态切换<transition>的name,比如前进用slide - left,后退用slide - right

    <transition :name="isBack ? 'slide - right' : 'slide - left'">
    <router - view></router - view>
    </transition>
  2. 写反向动画的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>显示子路由)
  • 子路由:HomeAbout(对应Layout中间区域的内容)

处理方式:

  1. 父路由组件(Layout)的<router - view>加过渡,负责父级布局切换的动画(比如整体淡入):
    <template>
    <div class="layout">
     <header>顶部栏</header>
     <transition name="parent - fade">
       <router - view></router - view> <!-- 这里渲染子路由(Home/About) -->
     </transition>
    </div>
    </template>
```
  1. 子路由组件内部如果还有嵌套(比如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,孙子级还能加其他动画,互不干扰~

动画和路由守卫结合,能玩出啥花样?

路由守卫(比如beforeEnterbeforeRouteEnter)能控制页面跳转的逻辑,和动画结合可以做更灵活的交互,举两个实用场景:

场景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动画:用样式声明过渡,适合简单的opacitytransform变化,写起来简洁。
  • JS动画:用JS代码控制每帧变化,适合复杂逻辑(比如链式动画、和GSAP/Anime.js等库结合)。

举个用GSAP库做动画的例子:

  1. <transition>绑定JS钩子:

    <transition
    @enter="enterHook"
    @leave="leaveHook"
    >
    <router - view></router - view>
    </transition>
  2. 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类名是否对应?(比如nameslide,CSS得是slide - enter - from,不能写成fade - enter - from
  • 元素是否被display:none等样式隐藏?(transitiondisplay切换支持不好,尽量用opacitytransform做过渡)

坑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 - activetransition时长要和动画逻辑匹配;用JS钩子必须调用done,保证动画完成后再切换~

看完这些问题,再面对路由切换动画是不是有思路多了?从基础淡入淡出,到不同路由差异化动画、返回反向动画、嵌套路由处理,再到和路由守卫、JS钩子结合,核心是利用<transition>enter/leave阶段,结合vue - router的路由变化特性(metamatched、路由方向等)。

记住几个关键:

  • 包裹<router - view>要用<transition>name对应CSS类名;
  • 动态动画靠路由meta和计算属性;
  • 前进后退判断用matched.length或路由方向;
  • 嵌套路由要给每个层级的<router - view>单独加过渡;
  • 踩坑时先检查结构、namemodekey这些细节~

现在可以动手试试,给项目里的页面切换加上丝滑动画,让用户体验起飞~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门