Vue 路由使用介绍以及添加转场动画

1,928 阅读8分钟

前言

随着手机性能的提升,如今移动端APP的用户体验越来越好了,页面流畅不卡顿,转场动画优雅又丝滑

而大部分的 web 页面,尽管功能上比起 10 年前 jQuery 时代丰富了不少,但似乎在使用体验上还是一尽难尽的地步,应用最广泛的动画可能是弹框组件了,毕竟 UI 库直接提供了动画


这是我的 模仿抖音 系列文章的第三篇:致力于用户体验优化,实现类似APP页面切换的动画效果,把其中的过程和原理以及有哪些坑写了出来,还请大家多多指教

第一篇:200行代码实现类似Swiper.js的轮播组件
第二篇:实现抖音 “视频无限滑动“效果
第四篇:Vue 有条件路由缓存,就像传统新闻网站一样
第五篇:Github Actions 部署 Pages、同步到 Gitee、翻译 README 、 打包 docker 镜像

最终效果

在线预览:dy.ttentau.top/

Github地址:github.com/zyronon/dou…

路由定义源码:routes.ts
转场动画源码:App.vue

思考

  • 如何判断是“前进”还是“后退”而使用不同的动画方式?

一、路由使用

1. 初始化路由

import Vue from 'vue'
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHashHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  }
})

...

const app = createApp(App)
app.use(router)

2. 定义路由

接下来就可以进行路由组件的映射:
为了践行模块化组件化思想,组件是从其他文件import进来


可以 静态 导入(在文件顶部用 import 导入组件) 使用

import Home from '../pages/home/index.vue'

静态导入的组件会 全部 默认打包到一个 js 文件中


也可以 动态 导入使用

{ path: '/home', component: () => import('@/pages/home/index.vue') },

动态的导入的 每一个 组件,会被单独打包成一个 js 文件,一对一的,定义了 50 个路由,就会打包出 50js 文件(当然我们可以在 vite.config.jsvue.config.js 里面配置打包分割,将组件进行分组统一打包到一个 js 文件中)


import Home from '../pages/home/index.vue'
import Test from '../pages/test/Test.vue'

//创建routes数组,然后传入 createRouter 的 ‘routes’ 配置中
const routes = [
  //每个路由映射一个组件
  { path: '/', redirect: '/home' },
  { path: '/test', component: Test },
  { path: '/home', component: Home },

3. 路由出口

App.vue

<router-view></router-view>

我们定义好了路由映射之后,还需要定义一个路由出口,即 <router-view></router-view>,这个标签放在哪个位置,哪个位置就会显示当前所匹配路由映射的 Vue 组件

默认我们是放在 App.vue 里面,这样就可以访问所有定义的 一级 路由了

4. 嵌套路由

上面只是路由的普通使用,只能访问一级页面,但我们做后台、管理系统等网站,基本上都有嵌套路由的要求
嵌套路由只需在 children 字段里放同样的路由映射即可

{
  path: '/layout',
  component: Layout,
  children: [
    { path: '/layout-child1', component: LayoutChild1 },
    { path: '/layout-child2', component: LayoutChild1 },
    { path: '/layout-child3', component: LayoutChild1 },
  ]
},

再定义子路由 出口,同样还是 <router-view></router-view> 标签,我们知道这个标签放在哪里,就会显示当前匹配的组件
我们在 App.vue 里面放了一个,那么 App.vue 就会显示当前所匹配路由映射的 Vue 组件

image.png

子路由的出口也是同样的,你想在在哪显示就放在哪里,我们的 Layout.vue 组件需要有子路由,那么就放在 Layout.vue 里面就行了

image.png

二、转场动画

我们需要用到 VueTransition 这个组件
具体使用请看官网,这里不详细介绍
cn.vuejs.org/guide/built…

还用到了 csstransform 属性,它允许你旋转,缩放,倾斜或平移给定元素,我们用它来平移两个路由页面
developer.mozilla.org/zh-CN/docs/…

还用到了 csstransition 属性,它可以使在不同 css 属性状态切换的时候产生过渡效果(可以理解为简单动画),我们用它来给 transform 加过渡效果
developer.mozilla.org/zh-CN/docs/…

1. Transition组件

Vue3
<router-view v-slot="{ Component }">
  <transition :name="transitionName">
     <component :is="Component" />
  </transition>
</router-view>

Vue2
<transition :name="transitionName">
    <router-view></router-view>
</transition>

需要注意的是,Vue3Vue2 的写法不同

  • transitionname 属性用于定义动画, Vue 会根据提供的 name 值去寻找对应的 css 类名,并在合适的时机添加、移除 css 类名
  • transition 的子元素只能有一个,并且当子元素符合以下条件时,会自动触发
    • 由 v-if 所触发的切换
    • 由 v-show 所触发的切换
    • 由特殊元素 <component> 切换的动态组件
    • 改变特殊的 key 属性

image.png

比如说,我们将 name 定义为 test,那么每次动画执行时,Vue 会自动给我们添加上图的 6 个 css 类名:test-enter-from test-enter-active test-enter-to test-leave-from test-leave-active test-leave-to


当我们前进时:
当前页面需要从 0(屏幕中间) 平移至 -100%(屏幕左侧) ,下个页面 需要从 100%(屏幕右侧) 平移至 0(屏幕中间)

当我们后退时:
当前页面需要从 0(屏幕中间) 平移至 100%(屏幕右侧) ,下个页面 需要从 -100%(屏幕左侧) 平移至 0(屏幕中间)

所以我们需要两种动画,一种是 前进 ,一种是 返回,不同状态展示不同动画

具体使用什么动画,我们用一个变量 transitionName 来定义

2.动画

我们在前进时,当前页面和下个页面的需要执行的动画是不同的,后退时也一样

这里直接放最终的 css 代码, 下面解释为什么这样写

前进动画

//页面最终/最初的状态:屏幕正中间
.go-enter-to,
.go-leave-from {
  transform: translate3d(0, 0, 0);
}
//动画持续时间
.go-enter-active,
.go-leave-active{
  transition: all 0.3s;
}
//离去动画,表示离去页面最终的css:屏幕左侧
.go-leave-to {
  transform: translate3d(-100%, 0, 0);
}
//进入动画,表示进入页面,初始的状态:屏幕右侧
.go-enter-from {
  transform: translate3d(100%, 0, 0);
}

后退动画

//和上面同理,只时 css 是反着的而已 
.back-enter-to,
.back-enter-from{
  transform: translate3d(0, 0, 0);
}
.back-enter-active,
.back-leave-active {
  transition: all 0.3s;
}
.back-enter-from {
  transform: translate3d(-100%, 0, 0);
}
.back-leave-to {
  transform: translate3d(100%, 0, 0);
}

3. 动画原理

两个页面平移动画的原理:

当我们前进时:
当前页面需要从 0(屏幕中间) 平移至 -100%(屏幕左侧) ,下个页面 需要从 100%(屏幕右侧) 平移至 0(屏幕中间)

当我们后退时:
当前页面需要从 0(屏幕中间) 平移至 100%(屏幕右侧) ,下个页面 需要从 -100%(屏幕左侧) 平移至 0(屏幕中间)


image.png

通过上图可以看到
一、当我们导航到下一页时,两个路由页面最外层的 div 是平级的,所以当动画执行时,会看到当前页面从屏幕中间向左平移出去了,但下个页面并没有从屏幕右侧向屏幕中间平移,而是在动画结束后,突然显示在屏幕中间。这是因为按照正常的文档流,两个页面最外层的 div 都是 block ,所以下个页面单起一行放在当前页面的正下方

解决办法:给每个页面加个统一的 class ,将页面统一设置为 position:absolute;,此时页面脱离文档流,不占空间,这样就不会把下一页挤下去,完成平滑过渡

二、由图可知,离去的页面 Vue 会自动给我们加上 xxx-leave-active, xxx-leave-to 这两个 class
而进入的页面,Vue 会自动给我们加上 xxx-enter-active, xxx-enter-to 这两个 class
还有这 xxx-leave-from, xxx-enter-from两个 class 是动画一开始就加上,然后立刻又删除了,截图里面展示不出来😅

image.png

所以,当我们知道 Vue 执行动画时有哪些 class,就可以对应的编写 css 过渡了

就拿 前进离去的页面 举例:

  • 动画开始:go-leave-from,然后又立马就删除了。所以我们可以在这个 class 编写用于启动过渡的 css ,用于表明这个页面的初始状态
//页面的初始状态
.go-leave-from {
  transform: translate3d(0, 0, 0);
}
  • 动画行程中:go-leave-active, go-leave-to,我们在这里定义整个过渡的时间,以及我们页面最终的 css 的样式
//整个过渡过程需要300毫秒
.go-leave-active {
  transition: all 0.3s;
}

//最终状态,当动画结束时,页面需要 平移到 -100% 的地方
.go-leave-to {
  transform: translate3d(-100%, 0, 0);
}

其他的动画,基本同理。需要注意的是,同一个动画中,离去的页面和进入的页面的 translate3d 是相反的

4. 区分是“前进”还是“后退”

$route 作为 vue 实例的一个响应式属性,和在 data 中写的属性本质上是一样的,都可以通过 this的方式拿到。既然你可以监听 data 中的属性变化,同样也可以监听 $route 的变化。watch 中监听的对象默认回调函数中的参数值就是 newVal, oldVal。作为 $route 属性来说当然也就是 tofrom 的概念了

to、from 是最基本的路由对象,分别表示从(from)某个页面跳转到(to)另一个页面,to.path(表示要跳转到的路由地址),from.path 同理

该功能的难点在于怎样分辨是“前进”还是“后退”?

还记得我们前面定义的 routes 数组吗?我们现在需要将每个路由按层级依次排序(暂不考虑嵌套路由的情况),依次排列每个路由的下一页、下下页...即保证每个“上一页”在“下一页”前面

总结下来就是:根据 routes 数组里定义的路由目录的顺序,在 watch 到路由变更的时候,用 to.pathfrom.pathroutes 数组里去查找下标,下标靠前的是上一页,靠后的是下一页,可由此判断是“前进”还是“后退”

Vue2写法

// watch $route 决定使用哪种过渡
watch: {
  '$route'(to, from) {
    //底部tab的按钮,跳转是不需要用动画的
    let noAnimation = ['/', '/home','/slide', '/me', '/attention', '/message', '/publish']
    if (noAnimation.indexOf(from.path) !== -1 && noAnimation.indexOf(to.path) !== -1) {
      return this.transitionName = ''
    }

    const toDepth = routes.findIndex(v => v.path === to.path)
    const fromDepth = routes.findIndex(v => v.path === from.path)
    this.transitionName = toDepth > fromDepth ? 'go' : 'back'
  },
},

Vue3写法

const route = useRoute()

// watch $route 决定使用哪种过渡
watch(
  () => route.path,
  (to, from) => {
    //底部tab的按钮,跳转是不需要用动画的
    let noAnimation = ['/', '/home','/slide', '/me', '/attention', '/message', '/publish']
    if (noAnimation.indexOf(from) !== -1 && noAnimation.indexOf(to) !== -1) {
      return transitionName.value = ''
    }
    const toDepth = routes.findIndex((v: RouteRecordRaw) => v.path === to)
    const fromDepth = routes.findIndex((v: RouteRecordRaw) => v.path === from)
    transitionName.value = toDepth > fromDepth ? 'go' : 'back'
  }
)

总结

至此我们已经学会了路由使用,以及如何在 Vue 中怎么给路由添加转场动画了,我文章中所介绍的只是普通的平移动画,大家如果想要其他样式的动画,只需要对照着那几个 Vue 定义的那几个 css 修改就可以了

本来想把 页面缓存 也一起写了,不过由于篇幅所致,很多人都是 太长不看,所以还是单独写一篇吧~

结束

以上就是文章的全部内容,感谢看到这里,希望对你有所帮助或启发!创作不易,如果觉得文章写得不错,可以点赞收藏支持一下,我会更新更多实用的前端知识与技巧,期待与你共同成长~