Skip to content

路由过渡动画 | Vue Router

概述

Vue Router 与 Vue 的过渡系统完美集成,允许你为路由切换添加平滑的动画效果。通过合理的过渡动画,可以显著提升用户体验和应用的专业感。

基本过渡配置

使用 Vue Transition 组件

vue
<template>
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

不同的过渡模式

1. 淡入淡出(Fade)

css
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

2. 滑动效果(Slide)

css
.slide-enter-active,
.slide-leave-active {
  transition: transform 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
}

.slide-leave-to {
  transform: translateX(-100%);
}

3. 缩放效果(Scale)

css
.scale-enter-active,
.scale-leave-active {
  transition: all 0.3s ease;
}

.scale-enter-from {
  transform: scale(0.9);
  opacity: 0;
}

.scale-leave-to {
  transform: scale(1.1);
  opacity: 0;
}

基于路由的过渡动画

根据路由元信息动态过渡

vue
<template>
  <router-view v-slot="{ Component, route }">
    <transition :name="route.meta.transition || 'fade'" mode="out-in">
      <component :is="Component" :key="route.path" />
    </transition>
  </router-view>
</template>

<script setup>
// 路由配置
const routes = [
  {
    path: '/',
    component: Home,
    meta: { transition: 'slide' }
  },
  {
    path: '/about',
    component: About,
    meta: { transition: 'fade' }
  }
]
</script>

根据路由方向动态过渡

vue
<template>
  <router-view v-slot="{ Component, route }">
    <transition :name="transitionName" mode="out-in">
      <component :is="Component" :key="route.path" />
    </transition>
  </router-view>
</template>

<script setup>
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const transitionName = ref('slide-left')

// 监听路由变化,决定过渡方向
watch(
  () => router.currentRoute.value,
  (to, from) => {
    const toDepth = to.meta.depth || 0
    const fromDepth = from.meta.depth || 0
    transitionName.value = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
)
</script>

<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: transform 0.3s ease;
}

.slide-left-enter-from {
  transform: translateX(100%);
}

.slide-left-leave-to {
  transform: translateX(-100%);
}

.slide-right-enter-from {
  transform: translateX(-100%);
}

.slide-right-leave-to {
  transform: translateX(100%);
}
</style>

高级过渡技巧

1. 交错动画(Staggered)

vue
<template>
  <router-view v-slot="{ Component, route }">
    <transition-group name="stagger" tag="div" class="page-container">
      <component :is="Component" :key="route.path" />
    </transition-group>
  </router-view>
</template>

<style>
.stagger-enter-active {
  transition: all 0.3s ease;
  transition-delay: calc(0.1s * var(--stagger-index));
}

.stagger-leave-active {
  transition: all 0.3s ease;
}

.stagger-enter-from {
  opacity: 0;
  transform: translateY(20px);
}

.stagger-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
</style>

2. 页面特定的过渡样式

vue
<script setup>
// 为不同页面设置不同的过渡延迟
const pageTransitions = {
  '/': { delay: 0, duration: 0.3 },
  '/about': { delay: 0.1, duration: 0.5 },
  '/contact': { delay: 0.2, duration: 0.4 }
}

function getTransitionStyle(route) {
  const config = pageTransitions[route.path] || pageTransitions['/']
  return {
    '--transition-delay': `${config.delay}s`,
    '--transition-duration': `${config.duration}s`
  }
}
</script>

<template>
  <router-view v-slot="{ Component, route }">
    <transition name="custom">
      <component 
        :is="Component" 
        :key="route.path"
        :style="getTransitionStyle(route)"
      />
    </transition>
  </router-view>
</template>

<style>
.custom-enter-active,
.custom-leave-active {
  transition: all var(--transition-duration) ease;
  transition-delay: var(--transition-delay);
}
</style>

实际应用场景

场景 1:移动端应用过渡

vue
<template>
  <div class="mobile-app">
    <nav class="tab-bar">
      <router-link to="/">首页</router-link>
      <router-link to="/discover">发现</router-link>
      <router-link to="/profile">我的</router-link>
    </nav>
    
    <router-view v-slot="{ Component, route }">
      <transition :name="getMobileTransition(route)" mode="out-in">
        <component :is="Component" :key="route.path" />
      </transition>
    </router-view>
  </div>
</template>

<script setup>
function getMobileTransition(route) {
  const tabOrder = ['/', '/discover', '/profile']
  const currentIndex = tabOrder.indexOf(route.path)
  const prevIndex = tabOrder.indexOf(history.state?.prevPath || '/')
  
  return currentIndex > prevIndex ? 'slide-left' : 'slide-right'
}
</script>

<style>
.mobile-app {
  position: relative;
  overflow: hidden;
}

.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  position: absolute;
  width: 100%;
  top: 0;
}

.slide-left-enter-from {
  transform: translateX(100%);
}

.slide-left-leave-to {
  transform: translateX(-100%);
}

.slide-right-enter-from {
  transform: translateX(-100%);
}

.slide-right-leave-to {
  transform: translateX(100%);
}
</style>

场景 2:管理后台过渡

vue
<template>
  <div class="admin-layout">
    <aside class="sidebar">
      <nav>
        <router-link to="/admin/dashboard">仪表盘</router-link>
        <router-link to="/admin/users">用户管理</router-link>
        <router-link to="/admin/settings">系统设置</router-link>
      </nav>
    </aside>
    
    <main class="content">
      <router-view v-slot="{ Component, route }">
        <transition name="page-fade" mode="out-in">
          <component :is="Component" :key="route.path" />
        </transition>
      </router-view>
    </main>
  </div>
</template>

<style>
.page-fade-enter-active {
  animation: pageFadeIn 0.4s ease;
}

.page-fade-leave-active {
  animation: pageFadeOut 0.3s ease;
}

@keyframes pageFadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pageFadeOut {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}
</style>

性能优化技巧

1. 硬件加速优化

css
.optimized-transition {
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000px;
}

.smooth-transition {
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}

2. 减少重排重绘

css
.efficient-transition {
  /* 只变换 opacity 和 transform,避免布局变化 */
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.avoid-layout-transition {
  /* 避免这些属性,它们会导致重排 */
  /* transition: width, height, margin, padding */
}

3. 适当的动画时长

css
/* 快速响应(移动端) */
.quick-transition {
  transition-duration: 0.2s;
}

/* 标准时长 */
.standard-transition {
  transition-duration: 0.3s;
}

/* 强调效果 */
.emphasized-transition {
  transition-duration: 0.5s;
}

最佳实践

1. 保持一致性

css
/* 定义统一的过渡变量 */
:root {
  --transition-duration-fast: 0.2s;
  --transition-duration-normal: 0.3s;
  --transition-duration-slow: 0.5s;
  --transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
}

.page-transition {
  transition: all var(--transition-duration-normal) var(--transition-timing);
}

2. 可访问性考虑

css
/* 尊重用户偏好 */
@media (prefers-reduced-motion: reduce) {
  .page-transition {
    transition: none;
  }
}

3. 优雅降级

javascript
// 检测浏览器支持情况
function supportsSmoothTransitions() {
  return 'transition' in document.documentElement.style
}

// 不支持的浏览器使用简单过渡
const transitionName = supportsSmoothTransitions() 
  ? 'smooth-slide' 
  : 'basic-fade'

故障排除

常见问题及解决方案

  1. 动画闪烁

    • 确保使用 mode="out-in"mode="in-out"
    • 检查元素定位是否正确
  2. 性能问题

    • 避免同时动画过多元素
    • 使用 will-change 属性优化
  3. 移动端卡顿

    • 使用硬件加速属性
    • 简化动画效果

🎯 总结:合理的路由过渡动画可以大大提升用户体验。通过精心设计的动画效果,可以让应用看起来更加专业和流畅。

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择