Vue - 实现h5过渡动效+虚拟任务缓存

前言

大家好, 今天给大家分享如何在h5端拥有app那样的丝滑切换动画. 例如在切换路由的时候, 每次都是很死板, 要么直接出现, 要么直接隐藏, 例如像下面这样.

显然这样很死板, 页面切换没有做任何的动画.

并且我们每次进入一个新的页面, 如果在接口在setup下或者生命周期函数中, 每次切换都是会发起请求. 但是如果是平凡的进入一个有众多数据的页面呢, 例如商品图片列表, 每次请求大量的数据, 会造成无用的请求.

这是我们的理想效果

手机端过渡动效以及组件缓存

明确目标

1.在切换路由时, 需要发生动画并且具有过渡的效果

2.预览当前页面后跳转并返回,需要不发送请求

分析功能

其一, 需要使用vue-router 提供的过渡动效, 能够结果路由跳转进行切换动画

其二, 还需要再返回到当前例如B组件时, 将B组件进行缓存, 从而减少重复的请求

这里需要使用keep-alive 组件缓存来实现, 同样我们看一下文档说明

按照官网描述, 这里是已经将两者结合, 也就是同时包含了动态路由的动画和组件缓存

其三, 虚拟任务栈的概念

思考一下, 这里需要做区分, 我是进入这个页面, 还是返回到这个页面. 也就是 我到底是push 呢 还是back呢 因为两者需要做相反反向的动画效果, 才可以显示的连贯, 并且管理这些对应的页面栈, 例如下图描述(先进后出, 后进先出)

其四, 管理虚拟任务栈

我们需要封装一个这样的栈, 来维护组件进入和退出的流程, 对于这样的虚拟任务栈, 我们可以通过数组的方法维护,从而实现先进后出

最后可以通过keep-alive 中的include把虚拟任务栈数组进行绑定, 从而实现任务栈缓存的概念

封装router-transtion

1.defineProps接收参数

告诉组件, 我的跳转类型[push, back , none]

告诉组件, 页面栈的最后一个组件的名称, 例如 home

js 复制代码
// h5端 切换动画+缓存
const props = defineProps({
  // 路由跳转类型, 是进入组件还是退出组件
  routerType: {
    type: String,
    default: 'none', // none: 不使用动画, push: 进入组件, back: 退出
    validator: (val) => {
      const arr = ['none', 'push', 'back']
      const res = arr.includes(val)
      if (!res) {
        throw new Error(`你的 routerType 必须是 ${arr.join('、')} 其中之一`)
      }
    }
  },
  // 首页的组件名称, 也就是页面栈的最后一个组件名称
  mainComponentName: {
    type: String,
    required: true
  }
})

2.参照官网书写缓存组件结构

这个没什么好说的cv官网

:name="transitionName" 代表transition动画组件使用动画指定的样式

:key="$route.fullPath" 保证路由的唯一性

js 复制代码
<template>
  <router-view v-slot="{ Component }">
    <transition :name="transitionName">
      <keep-alive>
        <component :is="Component" :key="$route.fullPath">                 </component>
      </keep-alive>
    </transition>
  </router-view>
</template>

3. 告诉transition执行的动画

因为可能是router.push 也可能是router.back 那么这两者的动画应该是相反的,

所以这里需要做区分, 并且是在路由前置守卫中执行, 接下来我们每次进行跳转的时候

css动画样式就是常规的左右移动50% 然后加过渡效果

js 复制代码
import { useRouter } from 'vue-router'
// 跳转动画
const transitionName = ref('')
/**
 * router的前置守卫
 */
router.beforeEach((to, form) => {
  // 定义transition跳转动画的名称
  transitionName.value = props.routerType
})

<style lang="scss" scoped>
// push页面时:新页面的进入动画
.push-enter-active {
  animation-name: push-in;
  animation-duration: 0.4s;
}
// push页面时:老页面的退出动画
.push-leave-active {
  animation-name: push-out;
  animation-duration: 0.4s;
}
// push页面时:新页面的进入动画
@keyframes push-in {
  0% {
    transform: translate(100%, 0);
  }
  100% {
    transform: translate(0, 0);
  }
}
// push页面时:老页面的退出动画
@keyframes push-out {
  0% {
    transform: translate(0, 0);
  }
  100% {
    transform: translate(-50%, 0);
  }
}

// 后退页面时:即将展示的页面动画
.back-enter-active {
  animation-name: back-in;
  animation-duration: 0.4s;
}
// 后退页面时:后退的页面执行的动画
.back-leave-active {
  animation-name: back-out;
  animation-duration: 0.4s;
}
// 后退页面时:即将展示的页面动画
@keyframes back-in {
  0% {
    width: 100%;
    transform: translate(-100%, 0);
  }
  100% {
    width: 100%;
    transform: translate(0, 0);
  }
}
// 后退页面时:后退的页面执行的动画
@keyframes back-out {
  0% {
    width: 100%;
    transform: translate(0, 0);
  }
  100% {
    width: 100%;
    transform: translate(50%, 0);
  }
}
</style>

4. 设置路由出口

因为是需要全局来控制跳转的类型, 我们需要将routerType定义在pinia或者vuex中并持久化, 防止刷新丢失

pinia部分代码

js 复制代码
import { ref } from 'vue'
import { defineStore } from 'pinia'
import type { User } from '@/types/user'

export const useUserStore = defineStore(
  'cp-counter',
  () => {
    // 1. 提供用户信息
    const user = ref<User>()

    // 2.修改用信息的方法
    const setUser = (val: User) => {
      user.value = val
    }

    // 3.清空用户,退出后使用
    const delUser = () => {
      user.value = undefined
    }
    return { user, setUser, delUser }
  },
  {
    // 开启持久化存储
    persist: true
  }
)

二级路由出口对应代码

注意, 跳转前需要往仓库设置routerType

:routerType="store.routerType" 代表当前的跳转方式

mianComponentName="home" 代表这是最底层的页面

js 复制代码
<script setup lang="ts">
import { useRouterStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const store = useRouterStore()

// 去home页面
const gotoHome = () => {
  store.changeRouterType('push')
  router.push('/home')
}
// 去user页面
const gotoUser = () => {
  store.changeRouterType('push')
  router.push('/user')
}
</script>

<template>
  <div class="layout-page">
    <!-- <router-view></router-view> -->
    <router-transition
      :routerType="store.routerType"
      mianComponentName="home"
    ></router-transition>
    <!-- tabber -->
    <van-tabbar route safe-area-inset-bottom fixed>
      <van-tabbar-item @click="gotoHome">
        <template #icon="{ active }">
          <cp-icon
            class="icon"
            :name="`home-index-${active ? 'active' : 'default'}`"
          ></cp-icon>
        </template>
        页面A</van-tabbar-item
      >
      <van-tabbar-item @click="gotoUser">
        <template #icon="{ active }">
          <cp-icon
            class="icon"
            :name="`home-notice-${active ? 'active' : 'default'}`"
          ></cp-icon>
        </template>
        页面B</van-tabbar-item
      >
    </van-tabbar>
  </div>
</template>

<style lang="scss" scoped>
::v-deep() {
  .van-tabbar {
    z-index: 100;
  }
}
.icon {
  font-size: 26px;
}
</style>

返回的back

js 复制代码
store.changeRouterType('back')
router.back()

5. 解决切换时动画问题

此时动画是出来了 但是 查看页面会出现一半白屏的现象

想要解决, 其实也很简单, 可以审查下元素, 看一下截图, 其实就是上一层的页面盖住了即将出现的页面, 我们只需要设置定位脱离标准流即可

但是, 如果这样设置, 我们缓存的每一个组件都是固定定位的, 这显然不合理

我们只需要在动画开始前加上 在动画结束后去除,

所以需要设置一个变量来控制是否增加定位的类

这里需要使用transition提供的钩子函数实现, 具体可以看下图

这里需要用到router-transtion组件中我们使用到的路由前置守卫+动态样式

js 复制代码
// 控制动画
const isAnimation = ref(false)
/**
 * 动画开始
 */
const beforEnter = () => {
  isAnimation.value = true
}
/**
 * 动画结束
 */
const afterLeave = () => {
  isAnimation.value = false
}
<transition
      :name="transitionName"
      @before-enter="beforEnter"
      @before-leave="afterLeave"
 >
 <component
    :is="Component"
    :key="$route.fullPath"
    :class="{ 'fixed top-0 left-0 w-screen z-10': isAnimation }"
 ></component>
</transition>

此时呢就可以实现动画的效果了, 但是不要忘了, 我们还需要处理组件的缓存, 而且是根据虚拟任务栈

keep-alive缓存

进入页面,需要把当前页面进入栈

退出页面,需要把当前页面从栈中删除

如果是首页,就清空栈,只保留mainComponentName 也就是home

1.将keep-alive 添加include属性

js 复制代码
<keep-alive :include="virtualTaskStack">

2. 执行以上三步进行进栈处理

js 复制代码
/**
 * 清空栈
 */
const clearStack = () => {
  virtualTaskStack.value = [props.mainComponentName]
}

// 跳转动画
const transitionName = ref('')
/**
 * router的前置守卫
 */
router.beforeEach((to, form) => {
  // 定义transition跳转动画的名称
  transitionName.value = props.routerType
  if (props.routerType === 'push') {
    // push页面时, 将新页面的组件名称添加到虚拟任务栈的最后一个
    virtualTaskStack.value.push(to.name)
  } else if (props.routerType === 'back') { 
    // 后退页面时, 将虚拟任务栈的最后一个组件名称删除
    virtualTaskStack.value.pop()
  }
  if (to.name === props.mainComponentName) {
    // 如果是首页, 则清空栈
    clearStack()
  }
})

3. 将缓存的组件设置name

注意 组件的name 需要和路由中的name 保持一致!

此时我们再看一下效果 返回到页面b 的打印不会再打印, 说明缓存成功

完结~手动撒花✿✿ヽ(°▽°)ノ✿

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
一 乐4 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd4 小时前
前端知识汇总(持续更新)
前端