Skip to content

移动端添加路由左右滑动动画

在问过 Google 之后,大致都是基于两种方案:

  • 使用全局路由守卫
  • 使用 watch 监听路由变化

要实现左右滑动的动画都是给路由添加类型,向左还是向右动画就通过一下方式:

  • 通过监听路由的 isBack 判断是否返回
  • 有直接判断父子路由关系的
  • ...
  • 给路由 meta 添加 index 属性,index 越大嵌套越深

我使用了最后一种方法。

思路

我之前按照网上的方法试过一遍,并没有解决问题,其中有个问题就是当有多个页面属于同一级,那怎样判断跳转呢?

这让我想到用堆栈的方式来存储: 存储进入的路由到 store 中,之后再判断 index 大小来确定向左还是向右,如果 to 和 from 的 index 相等,则是通过判断入栈的顺序来判断向左还是向右。

store page.ts

ts
import { defineStore } from 'pinia'
import { useStorage } from '@vueuse/core'
import { RouteLocationNormalized } from 'vue-router'

export type PageDataType = {
  routeStack?: RouteLocationNormalized[]
}

export const usePage = defineStore('page', () => {
  const pageData = useStorage<PageDataType>(
    'pageData',
    {
      routeStack: []
    },
    sessionStorage
  )

  const updateRouteStack = (
    route: RouteLocationNormalized,
    type: 'add' | 'remove'
  ) => {
    if (pageData.value.routeStack?.find((rs) => rs.path === route.path)) return

    if (type === 'add') {
      pageData.value.routeStack?.push(route)
    }
    if (type === 'remove') {
      pageData.value.routeStack = pageData.value.routeStack?.filter(
        (rs) => rs.path !== route.path
      )
    }
  }

  return {
    pageData,
    updateRouteStack
  }
})

App.vue 实现动画逻辑

vue
<template>
  <div>
    <RouterView v-slot="{ Component }">
      <Transition :name="transitionName">
        <component :is="Component" v-if="isRouterLoaded" />
      </Transition>
    </RouterView>
  </div>
</template>

<script setup lang="ts">
import { usePage } from '@/store/modules/page'

const route = useRoute()
const router = useRouter()
const { pageData, updateRouteStack } = usePage()
const { routeStack } = pageData

const transitionName = ref<string>()

watch(
  () => router.currentRoute.value,
  (to, from) => {
    updateRouteStack(to, 'add')
    if (to.meta.index > from.meta.index) {
      transitionName.value = 'slide-left'
    } else if (to.meta.index < from.meta.index) {
      transitionName.value = 'slide-right'
    } else if (to.meta.index === from.meta.index) {
      // 判断路由进栈后的顺序大小
      const toIndex = routeStack?.findIndex((rs) => rs.path === to.path) || -1
      const fromIndex =
        routeStack?.findIndex((rs) => rs.path === from.path) || -1

      if (toIndex > fromIndex) transitionName.value = 'slide-left'
      else transitionName.value = 'slide-right'

      // 路由从栈中抽出
      updateRouteStack(to, 'remove')
    } else {
      transitionName.value = ''
    }
  }
)

const isRouterLoaded = computed(() => {
  if (route.name !== null) return true
  return false
})
</script>

动画样式

在全局样式文件中添加以下样式

scss
.slide-right-enter-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-left-leave-active {
  position: absolute;
  transition: all 0.5s;
  will-change: transform;
}

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

.slide-right-leave-to {
  z-index: 10;
  transform: translateX(100%);
}

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

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

转载请标注本站原文地址