移动端路由切换解决方案 —— 虚拟任务栈让你的 H5 像APP一样丝滑

2024-06-02 04:20

本文主要是介绍移动端路由切换解决方案 —— 虚拟任务栈让你的 H5 像APP一样丝滑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

01: 前言

02: 通用组件:trigger-menu 和 trigger-menu-item 构建方案分析

03: 通用组件:构建 trigger-menu 和 trigger-menu-item 

04: 前台业务下 H5 的应用场景 

05: 通用组件:transition-router-view 构建方案分析 与 虚拟任务栈

过渡动画

组件缓存

小结

06: 通用组件:transition-router-view 构建方案之过渡动效

07: 通用组件:处理过渡动效展示样式错误的问题

08: 通用组件:虚拟任务栈处理

09: 通用组件:记录页面滚动位置

10: 总结


 

01: 前言

移动端的应用应该以什么样的形式进行展示呢?

它的展示形式如何区分浏览器端,又为什么要进行区分?

虚拟任务栈又是什么?

02: 通用组件:trigger-menu 和 trigger-menu-item 构建方案分析

 

这块内容充当了移动端中的 TabBar 的作用,我们期望把它封装成一个通用的组件。

接下来我们就需要来分析一下这个“TabBar”,我们把它叫做 trigger-menu 的构建方案。

我们期望将来 trigger-menu 可以以下面的形式进行使用:

<m-trigger-menuv-if="isMobileTerminal"class="fixed bottom-6 m-auto left-0 right-0 w-[220px]"
><m-trigger-menu-itemicon="home"iconClass="fill-zinc-900 dark:fill-zinc-200">首页</m-trigger-menu-item>……
</m-trigger-menu>

也就是说,它被分成了两个组件:trigger-menu 和 trigger-menu-item。

其中 trigger-menu 表示整个的功能区域,trigger-menu-item 表示其中的一项。

因此我们需要针对这两个组件分别进行分析:

        1. trigger-menu:对于它而言,只起到一个 包裹容器 的作用,所以我们只需要提供一个对应的插槽即可。

        2. trigger-menu-item:起到了对应的展示作用,展示包括了 icon 和 文字。所以内部应该存在 svg-icon 用来展示图片;存在一个插槽用来展示文字。

到这里,我们基本分析完成了这两个组件的构建方案,整体还是比较简单的。

03: 通用组件:构建 trigger-menu 和 trigger-menu-item 

- src/libs
- - trigger-menu
- - - index.vue
- - trigger-menu-item
- - - index.vue
// src/libs/trigger-menu/index.vue<template><divclass="min-w-[180px] bg-white dark:bg-zinc-800 rounded-full shadow flex items-center justify-between px-2 py-1"><slot /></div>
</template><script setup></script>
// src/libs/trigger-menu-item/index.vue<template><divclass="w-5 flex flex-col items-center justify-center col mx-0.5"@click="onItemClick"><m-svg-icon:name="icon":fillClass="iconClass"class="w-2 h-2"></m-svg-icon><p class="text-sm mt-0.5" :class="textClass"><slot /></p></div>
</template><script setup>
import { useRouter } from 'vue-router'const props = defineProps({icon: {type: String,required: true},iconClass: {type: String},textClass: {type: String,default: 'text-zinc-900 dark:text-zinc-200'},to: {type: String}
})const router = useRouter()
const onItemClick = () => {if (!props.to) {return}router.push(props.to)
}
</script><style lang="scss" scoped></style>

使用组件:

// src/views/main/index.vue<m-trigger-menuv-if="isMobileTerminal"class="fixed bottom-6 m-auto left-0 right-0 w-[220px]"
><m-trigger-menu-itemicon="home"iconClass="fill-zinc-900 dark:fill-zinc-200">首页</m-trigger-menu-item><m-trigger-menu-itemv-if="$store.getters.token"icon="vip"iconClass="fill-zinc-400 dark:fill-zinc-500"textClass="text-zinc-400 dark:text-zinc-500"@click="onVipClick">VIP</m-trigger-menu-item><m-trigger-menu-itemicon="profile"iconClass="fill-zinc-400 dark:fill-zinc-500"textClass="text-zinc-400 dark:text-zinc-500"@click="onMyClick">{{ $store.getters.token ? '我的' : '登录' }}</m-trigger-menu-item>
</m-trigger-menu>

04: 前台业务下 H5 的应用场景 

通常情况下,我们说起移动端项目指的一般是两种:

        1. 原生 APP

        2. H5 网页

        此时我们所做的这个移动端,指的就是 H5 网页。该内容依然是以网页为主,但是被运行到手机端之中。

H5 网页应用到手机端的时候,通常也是有两种运行的形式:

        1. 直接在手机端浏览器中运行。这种使用情况相对较少。在这种情况下,用户明显的知道这就是一个网页。

        2. 在原生组件 WebView 中运行(混合开发)。通常会被嵌入到 APP 之中。这种使用情况比较多,以下内容主要针对此种情况进行说明。

        这种情况下,用户会认为该内容是 APP 的一部分,不会把它当成网页。而是会把它当做 原生APP。一旦用户把它作为 APP 进行衡量,就会对应用有更高的要求。

        路由之间的跳转应该具备对应的动画,并且上一个页面的状态应该被缓存(页面的滚动状态和数据视图)。想要实现这样的功能,我们必须使用到之前提到过的 过渡动效。

        我们期望把整个的一套移动端的跳转全部封装为一个 通用组件,期望通过这个通用组件来实现 移动端下 H5 页面的过渡功能

05: 通用组件:transition-router-view 构建方案分析 与 虚拟任务栈

        根据上一小节的分析可知,我们接下来要实现 移动端的过渡动效,以达到一个良好的移动端用户交互体验。 接下来尝试分析一下它的实现方案。

它的实现方案整体分为两种:

        1. 过渡动画。

        2. 组件缓存。

过渡动画

        想要实现这个功能,我们需要使用到 过渡动效 这个功能,它描述了两个路由之间进行过渡时的动画效果。在这个功能的官方描述中,主要包含了三个对应的组件:

<!-- 路由出口 -->
<router-view v-slot="{ Component }"><!-- 动画 --><transition name="fade"><!-- 动态组件 --><component :is="Component" /></transition>
</router-view>

使用其中的 transition 就可以实现跳转时的动画效果。

大家需要注意过渡动画分为两部分:

        1. 进入动画。

        2. 退出动画。

这里 transition 的 name 需要是动态的,以此来表示对应的两种动画形式。

组件缓存

因为同时我们要使用到 组件缓存,所以我们还需要依赖 keep-alive

这四个组件想要在一起工作,将要按照以下的方式进行组合:

<!-- 路由出口 -->
<router-view v-slot="{ Component }"><!-- 动画组件 --><transition name="transitionName"><!-- 缓存组件 --><keep-alive><!-- 动态组件 --><component :is="Component" :key="$route.fullPath" />// 同域名下的跳转。比如(动态路由 /detail/:id)</keep-alive></transition>
</router-view>

有一点大家需要注意:不是所有的组件都需要缓存

我们把:组件的进入和退出流程,比作一个栈

只有进入到栈中的组件才需要被缓存,就像 Android 中的 任务栈 概念一样,如下图所示:

在当前咱们移动端的组件处理中,我们同样期望有一个这样的栈来维护我们的组件进入和退出流程。我们把这样的一套流程,称作:虚拟任务栈。 

对于这样的一个虚拟任务栈而言,我们可以通过 数组 进行维护。因为数组与栈的概念有相似之处,即:先进后出 的流程。

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

小结

本小节我们分析了接下来要去处理的移动端页面跳转功能。想要实现这样的功能,主要分成了两大步:

        1. 过渡动画:使用 过渡动效 实现。

        2. 组件缓存:虚拟任务栈 - 数组 配合 keep-alive 中的 include 实现。 

06: 通用组件:transition-router-view 构建方案之过渡动效

- src/libs
- - transition-router-view
- - - index.vue
// src/libs/transition-router-view/index.vue<template><!-- 路由出口 --><router-view v-slot="{ Component }"><!-- 动画组件 --><transition:name="transitionName"@before-enter="beforeEnter"@after-leave="afterLeave"><!-- 缓存组件 --><keep-alive :include="virtualTaskStack"><component:is="Component":class="{ 'fixed top-0 left-0 w-screen z-50': isAnimation }":key="$route.fullPath"/></keep-alive></transition></router-view>
</template><script>
import { ref } from 'vue'
import { useRouter } from 'vue-router'// 无需监听路由的各种状态(在 PC 端下)
const NONE = 'none'
// 路由进入
const PUSH = 'push'
// 路由退出
const BACK = 'back'
// 路由跳转的 enum
const ROUTER_TYPE_ENUM = [NONE, PUSH, BACK]
</script><script setup>
const props = defineProps({// 路由跳转的类型,对应 ROUTER_TYPE_ENUMrouterType: {type: String,default: NONE,validator(val) {const result = ROUTER_TYPE_ENUM.includes(val)if (!result) {throw new Error(`你的 routerType 必须是 ${ROUTER_TYPE_ENUM.join('、')} 中的一个`)}return result}},// 首页的组件名称,对应任务栈中的第一个组件mainComponentName: {type: String,required: true}
})// 任务栈
const virtualTaskStack = ref([props.mainComponentName])const router = useRouter()
// 跳转动画
const transitionName = ref('')
/*** 监听路由变化*/
router.beforeEach((to, from) => {// 定义当前动画名称transitionName.value = props.routerTypeif (props.routerType === PUSH) {// 入栈virtualTaskStack.value.push(to.name)} else if (props.routerType === BACK) {// 出栈virtualTaskStack.value.pop()}// 进入首页默认清空栈if (to.name === props.mainComponentName) {clearTask()}
})// 处理动画状态变化
const isAnimation = ref(false)
const beforeEnter = () => {isAnimation.value = true
}
const afterLeave = () => {isAnimation.value = false
}/*** 清空栈*/
const clearTask = () => {virtualTaskStack.value = [props.mainComponentName]
}
</script><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>
// src/store/modules/app.jsimport { ALL_CATEGORY_ITEM } from '@/constants'export default {namespaced: true,state: () => ({// 路由跳转类型routerType: 'none'}),mutations: {/*** 修改 routerType*/changeRouterType(state, newType) {state.routerType = newType}}
}
// src/store/getters.jsimport { isMobileTerminal } from '@/utils/flexible'export default {……// 路由跳转方式routerType: (state) => {// 在 PC 端下,永远为 noneif (!isMobileTerminal.value) {return 'none'}return state.app.routerType}
}
// src/App.vue 使用组件<m-transition-router-viewmainComponentName="home":routerType="$store.getters.routerType"
></m-transition-router-view>
// 除了 libs 中组件包含的跳转、前往首页的跳转 之外,其他的跳转进行修改store.commit('app/changeRouterType', 'push')
router.push('/login')store.commit('app/changeRouterType', 'back')
router.back()

07: 通用组件:处理过渡动效展示样式错误的问题

// src/libs/transition-router-view/index.vue<template><transition@before-enter="beforeEnter"@after-leave="afterLeave"><component:class="{ 'fixed top-0 left-0 w-screen z-50': isAnimation }"/></transition>
</template>
<script setup>
// 处理动画状态变化
const isAnimation = ref(false)
const beforeEnter = () => {isAnimation.value = true
}
const afterLeave = () => {isAnimation.value = false
}
</script>

08: 通用组件:虚拟任务栈处理

目前路由的跳转动画已经执行成功,下面来处理对应的组件缓存。

对于组件缓存而言,我们将通过 keep-alive 构建一个虚拟任务栈。

// src/libs/transition-router-view/index.vue<template><keep-alive :include="virtualTaskStack"></keep-alive></template><script setup>// 任务栈
const virtualTaskStack = ref([props.mainComponentName])/*** 监听路由变化*/
router.beforeEach((to, from) => {……if (props.routerType === PUSH) {// 入栈virtualTaskStack.value.push(to.name)} else if (props.routerType === BACK) {// 出栈virtualTaskStack.value.pop()}// 进入首页默认清空栈if (to.name === props.mainComponentName) {clearTask()}
})/*** 清空栈*/
const clearTask = () => {virtualTaskStack.value = [props.mainComponentName]
}
</script>
// 注意 各个单文件组件 的命名<script>
export default {name: 'home'
}
</script>

特殊情况处理

强制在复用的视图之间进行过渡

情况:从一个详情页跳转到另一个详情页,"/pins/:id"。 两个页面对应一个组件,缓存可能会出现问题,跳转可能也会有问题。

解决:<component :key="$route.fullPath" />

09: 通用组件:记录页面滚动位置

keep-alive 组件只能够帮助我们缓存组件,但是不能够记录页面的滚动位置。

如果我们想要记录页面滚动位置的话,需要在 通用组件外 单独处理。可以使用 useScroll 进行记录。

目前在当前应用中,我们仅需要保存 home 页面的滚动位置即可。

// src/views/main/index.vue<script setup>
import { useScroll } from '@vueuse/core'/*** 记录页面滚动位置*/
const containerTarget = ref(null)
const { y: containerTargetScrollY } = useScroll(containerTarget)
// 被缓存的组件再次可见,会回调 onActivated 方法
onActivated(() => {if (!containerTarget.value) {return}containerTarget.value.scrollTop = containerTargetScrollY.value
})
</script>

10: 总结

到这里咱们的整个移动端路由切换就已经全部完成了,本文章主要涉及到了两个通用组件的构建:

        1. trigger-menu && trigger-menu-item

        2. transition-router-view:

                1. 动画效果

                2. 组件缓存

                3. 滚动位置缓存

这篇关于移动端路由切换解决方案 —— 虚拟任务栈让你的 H5 像APP一样丝滑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023017

相关文章

我在移动打工的日志

客户:给我搞一下录音 我:不会。不在服务范围。 客户:是不想吧 我:笑嘻嘻(气笑) 客户:小姑娘明明会,却欺负老人 我:笑嘻嘻 客户:那我交话费 我:手机号 客户:给我搞录音 我:不会。不懂。没搞过。 客户:那我交话费 我:手机号。这是电信的啊!!我这是中国移动!! 客户:我不管,我要充话费,充话费是你们的 我:可是这是移动!!中国移动!! 客户:我这是手机号 我:那又如何,这是移动!你是电信!!

EasyPlayer.js网页H5 Web js播放器能力合集

最近遇到一个需求,要求做一款播放器,发现能力上跟EasyPlayer.js基本一致,满足要求: 需求 功性能 分类 需求描述 功能 预览 分屏模式 单分屏(单屏/全屏) 多分屏(2*2) 多分屏(3*3) 多分屏(4*4) 播放控制 播放(单个或全部) 暂停(暂停时展示最后一帧画面) 停止(单个或全部) 声音控制(开关/音量调节) 主辅码流切换 辅助功能 屏

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

js异步提交form表单的解决方案

1.定义异步提交表单的方法 (通用方法) /*** 异步提交form表单* @param options {form:form表单元素,success:执行成功后处理函数}* <span style="color:#ff0000;"><strong>@注意 后台接收参数要解码否则中文会导致乱码 如:URLDecoder.decode(param,"UTF-8")</strong></span>

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u

简单的角色响应鼠标而移动

actor类 //处理移动距离,核心是找到角色坐标在世界坐标的向量的投影(x,y,z),然后在世界坐标中合成,此CC是在地面行走,所以Y轴投影始终置为0; using UnityEngine; using System.Collections; public class actor : MonoBehaviour { public float speed=0.1f; CharacterCo

解决Office Word不能切换中文输入

我们在使用WORD的时可能会经常碰到WORD中无法输入中文的情况。因为,虽然我们安装了搜狗输入法,但是到我们在WORD中使用搜狗的输入法的切换中英文的按键的时候会发现根本没有效果,无法将输入法切换成中文的。下面我就介绍一下如何在WORD中把搜狗输入法切换到中文。

明明的随机数处理问题分析与解决方案

明明的随机数处理问题分析与解决方案 引言问题描述解决方案数据结构设计具体步骤伪代码C语言实现详细解释读取输入去重操作排序操作输出结果复杂度分析 引言 明明生成了N个1到500之间的随机整数,我们需要对这些整数进行处理,删去重复的数字,然后进行排序并输出结果。本文将详细讲解如何通过算法、数据结构以及C语言来解决这个问题。我们将会使用数组和哈希表来实现去重操作,再利用排序算法对结果

H5漂流瓶社交系统源码

一个非常有创意的H5漂流瓶社交系统源码,带完整前端h5和后台管理系统。 环境:Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 代码下载

UE5 半透明阴影 快速解决方案

Step 1: 打开该选项 Step 2: 将半透明材质给到模型后,设置光照的Shadow Resolution Scale,越大,阴影的效果越好