高阶路由过渡处理方案 —— 浏览器堆栈主动介入

news2024/11/24 17:54:11

目录

01: 前言

02: VueRouter 过渡动效可行性分析

03: 主动介入浏览器堆栈管理,分析可行性方案

04: 主动介入浏览器堆栈管理

05: 基于 GSAP 实现高阶路由过渡动画分析 

06: 基于 GSAP 实现高阶路由过渡动画处理

07: 通用组件:navbar 构建方案分析

08: 通用组件:构建 navbar

09: 基于 navbar 处理响应式的 pins 页面

10: 处理刷新空白问题 

11: 总结 


 

01: 前言

        在 vue 中,两个路由进行跳转的时候,会为其增加一些跳转的过渡动画,这是一个非常常见的需求。通常情况下,这种过渡动画,我们可以使用 vue-router 的过渡动效 进行实现。

        对于咱们的项目而言,当我们 item 中点击进入 详情页 的时候,我们也希望可以有一个对应的过渡动效。从而提升用户体验。

我们期望这个过渡动效可以:

        1. 同时在 PC 端 和 移动端 生效。

        2. 进入新页面时:在点击的具体 item 中呈现 由小到大的缩放动画。

        3. 退出新页面时:呈现 由大到小的缩放动画 缩回至点击的具体 item 中。

这样的一个过渡动效,我们应该如何进行实现呢?

直接通过 vue-router 的过渡动效 可以实现吗?

如果不行的话,我们又应该如何去做呢?

02: VueRouter 过渡动效可行性分析

        接下来我们要实现的是:item 到详情页的路由过渡动效。这样的一个过渡动效,我们如何去做呢?通常针对这种功能,我们首先都会想到 vue-router 提供的 过渡动效。 

        想要判断这个问题,我们首先需要搞明白 vue-router 的过渡动效的过渡机制是什么?然后再根据这个机制来分析可行性。

<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
  <transition :name="route.meta.transition">
    <component :is="Component" />
  </transition>
</router-view>

以上这段代码是实现过渡动效的关键代码。

其中涉及到了三个组件:

1. router-view:路由出口

2. transition:动画组件

3. component:动态组件

据此可以得知,vue-router 过渡动效产生的关键就是 transition 动画组件。

同时我们知道对于 transition 而言,它能够产生动画的关键,其实是其内部元素 component 的动态组件切换。

但是这样的切换,它一定是基于整个 页面组件的。也就是说如果我们利用这种过渡动效,一定是:从 home 页面到 detail 页面 的整体页面组件的切换。

所以:是 无法 实现咱们期望的这种路由过渡效果的。 

03: 主动介入浏览器堆栈管理,分析可行性方案

        根据上一小节的分析,我们知道通过 vue-router 的过渡动效是无法实现咱们期望的路由切换效果的。那么应该如何去做?

        想要搞明白咱们的可行性方案,首先得先搞清楚 什么是路由跳转?所谓路由的跳转无非指的是两部分:

        1. 浏览器的 url 发生了变化。

        2. 浏览器中展示的页面组件发生了变化。

只要满足这两点,我们就认为 路径进行了跳转

        所以说,我们可不可以换个思路,我们 不去进行真实的路由跳转,而是 先修改浏览器的 URL,再切换展示的页面(以组件的形式覆盖整个浏览器可视区域)。这样对于用户而言,是不是就完成了整个路由的跳转工作。

这样我们的具体问题就变成了:

        1. 如何让浏览器的 url 发生变化,但是不跳转页面。

        2. 如何让一个新的组件以包含动画的形式进行展示。

想要完成第一个功能我们可以使用:History.pushState() 方法。而第二个功能我们可以使用  这个  GSAP 动画库进行实现。

04: 主动介入浏览器堆栈管理

// src/views/main/components/list/item.vue

<script setup>
const emits = defineEmits(['click'])

/*
 * 进入详情页点击事件
 */
const onToPinsClick = () => {
    emits('click', {
        id: props.data.id
    })
}
</script>
// src/views/main/components/list/index.vue

<template>
    <itemVue @click="onToPins" />
</template>

<script setup>
/*
 * 进入 pins
 */
const onToPins = (item) => {
    history.pushState(null, null, `/pins/${item.id}`)
}
</script>

05: 基于 GSAP 实现高阶路由过渡动画分析 

当 url 发生变化之后,我们接下来就只需要处理对应的动画就可以了。

动画的处理我们依赖于 GSAP 进行实现。对于 GSAP 而言,主要依赖两个方法:

        1. gsap.set(): 这个方法通常使用在动画开始之前,表示设置动画开始前的元素属性。

        2. gsap.to(): 这个方法表示 最终元素展示的状态

GSAP 会基于 set 和 to 的状态,来自动执行中间的补间动画。

所以我们只需要:

        1. 创建一个对应的组件,使用 transition 进行包裹。

        2. 计算出 set 时,组件元素对应的样式属性。

        3. 计算出 to 时,组件元素对应的样式属性。

然后就可以由 GSAP 自动实现对应的补间动画了。

06: 基于 GSAP 实现高阶路由过渡动画处理

- src/views
- - pins
- - - components
- - - - pins.vue
- - - index.vue
npm install --save gsap@3.9.1
// src/views/main/components/list/index.vue

<template>
    <!-- 大图详情处理 -->
    <transition
      :css="false"
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
    >
      <pins-vue v-if="isVisiblePins" :id="currentPins.id" />
    </transition>
</template>
<script setup>
// 控制 pins 展示
const isVisiblePins = ref(false)
// 当前选中的 pins 属性
const currentPins = ref({})
/**
 * 进入 pins
 */
const onToPins = (item) => {
  history.pushState(null, null, `/pins/${item.id}`)
  currentPins.value = item
  isVisiblePins.value = true
}

/**
 * 监听浏览器后退按钮事件
 */
useEventListener(window, 'popstate', () => {
  isVisiblePins.value = false
})

/**
 * 进入动画开始前
 */
const beforeEnter = (el) => {
  gsap.set(el, {
    scaleX: 0,
    scaleY: 0,
    transformOrigin: '0 0',
    translateX: currentPins.value.localtion?.translateX,
    translateY: currentPins.value.localtion?.translateY,
    opacity: 0
  })
}
/**
 * 进入动画执行中
 */
const enter = (el, done) => {
  gsap.to(el, {
    duration: 0.3,
    scaleX: 1,
    scaleY: 1,
    opacity: 1,
    translateX: 0,
    translateY: 0,
    onComplete: done
  })
}
/**
 * 离开动画执行中
 */
const leave = (el, done) => {
  gsap.to(el, {
    duration: 0.3,
    scaleX: 0,
    scaleY: 0,
    x: currentPins.value.localtion?.translateX,
    y: currentPins.value.localtion?.translateY,
    opacity: 0
  })
}
</script>
// src/views/main/components/list/item.vue
<template>
    <div @click="onToPinsClick" />
</template>
<script setup>
/**
 * 查看 vueuse 的源代码
 *(https://github.com/vueuse/vueuse/blob/main/packages/core/useElementBounding/index.ts)
 * 发现 useElementBounding 方法是仅在 window 的 scroll 时被触发,
 * 所以在移动端状态下会导致 useElementBounding 的返回值不再具备响应性。从而计算失败。
 * 所以我们可以修改 imgContainerCenter 为一个方法,
 * 利用 el.getBoundingClientRect 方法获取动态的 x、y、width、height , 从而进行正确的计算。
 */
const imgContainerCenter = () => {
  const {
    x: imgContainerX,
    y: imgContainerY,
    width: imgContainerWidth,
    height: imgContainerHeight
  } = imgTarget.value.getBoundingClientRect()
  return {
    translateX: parseInt(imgContainerX + imgContainerWidth / 2),
    translateY: parseInt(imgContainerY + imgContainerHeight / 2)
  }
}
/**
 * 进入详情点击事件
 */
const onToPinsClick = () => {
  emits('click', {
    id: props.data.id,
    localtion: imgContainerCenter()
  })
}
</script>

知识点讲解:

        你可以通过监听 <Transition> 组件事件的方式在过渡过程中挂上钩子函数。

        这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。

        在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。

        在有了 :css="false" 后,我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 @enter 和 @leave 钩子来说,回调函数 done 就是必须的。否则,钩子将被同步调用,过渡将立即完成。

07: 通用组件:navbar 构建方案分析

接下来我们就需要处理 pins 中对应的页面样式了。

        pins 的页面样式同时可以应用到 pc端 和 移动端。而在 移动端 中,则会展示对应的 navbar 的内容,所以我们首先构建出 navbar 通用组件,然后基于 navbar 构建对应的 pins 样式。

对于 navbar 而言:

        1. 它分为 左、中、右 三个大的部分,三个部分都可以通过插槽进行指定。

        2. 左、右 两边的插槽可以自定义点击事件。

        3. 同时 navbar 有时候会存在吸顶效果,所以我们最好还可以通过一个 prop 指定对应的吸顶展示。

分析完成之后,接下来实现对应的 navbar 构建。 

08: 通用组件:构建 navbar

- src/libs
- - navbar
- - - index.vue
// src/libs/navbar/index.vue

<template>
  <div
    class="w-full h-5 border-b flex items-center z-10 bg-white dark:bg-zinc-800 border-b-zinc-200 dark:border-b-zinc-700"
    :class="[sticky ? 'sticky top-0 left-0' : 'relative']"
  >
    <!-- 左 -->
    <div
      class="h-full w-5 absolute left-0 flex items-center justify-center"
      @click="onClickLeft"
    >
      <slot name="left">
        <m-svg-icon
          name="back"
          class="w-2 h-2"
          fillClass="fill-zinc-900 dark:fill-zinc-200"
        />
      </slot>
    </div>
    <!-- 中 -->
    <div
      class="h-full flex items-center justify-center m-auto font-bold text-base text-zinc-900 dark:text-zinc-200"
    >
      <slot></slot>
    </div>
    <!-- 右 -->
    <div
      class="h-full w-5 absolute right-0 flex items-center justify-center"
      @click="onClickRight"
    >
      <slot name="right" />
    </div>
  </div>
</template>
<script setup>
import { useRouter } from 'vue-router'

const props = defineProps({
  clickLeft: {
    type: Function
  },
  clickRight: {
    type: Function
  },
  sticky: {
    type: Boolean
  }
})
const router = useRouter()
/**
 * 左侧按钮点击事件
 */
const onClickLeft = () => {
  if (props.clickLeft) {
    props.clickLeft()
    return
  }
  router.back()
}

/**
 * 右侧按钮点击事件
 */
const onClickRight = () => {
  if (props.clickRight) {
    props.clickRight()
  }
}
</script>

<style lang="scss" scoped></style>

09: 基于 navbar 处理响应式的 pins 页面

// src/views/pins/components/pins.vue

<template>
  <div
    class="fixed left-0 top-0 w-screen h-screen z-20 backdrop-blur-4xl bg-white dark:bg-zinc-800 pb-2 overflow-y-auto xl:p-2 xl:bg-transparent"
  >
    <!-- 移动端下展示 navbar -->
    <m-navbar
      v-if="isMobileTerminal"
      sticky
      @clickLeft="onPop"
      @clickRight="onPop"
    >
      {{ pexelData.title }}
      <template #right>
        <m-svg-icon
          name="share"
          class="w-3 h-3"
          fillClass="fill-zinc-900 dark:fill-zinc-200"
        ></m-svg-icon>
      </template>
    </m-navbar>
    <!-- pc 端下展示关闭图标 -->
    <m-svg-icon
      v-else
      name="close"
      class="w-3 h-3 ml-1 p-0.5 cursor-pointer duration-200 rounded-sm hover:bg-zinc-100 absolute right-2 top-2"
      fillClass="fill-zinc-400"
      @click="onPop"
    ></m-svg-icon>

    <div class="xl:w-[80%] xl:h-full xl:mx-auto xl:rounded-lg xl:flex">
      <img
        class="w-screen mb-2 xl:w-3/5 xl:h-full xl:rounded-tl-lg xl:rounded-bl-lg"
        :src="pexelData.photo"
      />
      <div
        class="xl:w-2/5 xl:h-full xl:bg-white xl:dark:bg-zinc-900 xl:rounded-tr-lg xl:rounded-br-lg xl:p-3"
      >
        <div v-if="!isMobileTerminal" class="flex justify-between mb-2">
          <m-svg-icon
            name="share"
            class="w-4 h-4 p-1 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-800 duration-300 rounded"
            fillClass="fill-zinc-900 dark:fill-zinc-200"
          ></m-svg-icon>

          <m-button
            class=""
            type="info"
            icon="heart"
            iconClass="fill-zinc-900 dark:fill-zinc-200"
          />
        </div>
        <!-- 标题 -->
        <p
          class="text-base text-zinc-900 dark:text-zinc-200 ml-1 font-bold xl:text-xl xl:mb-5"
        >
          {{ pexelData.title }}
        </p>
        <!-- 作者 -->
        <div class="flex items-center mt-1 px-1">
          <img
            v-lazy
            class="h-3 w-3 rounded-full"
            :src="pexelData.avatar"
            alt=""
          />
          <span class="text-base text-zinc-900 dark:text-zinc-200 ml-1">{{
            pexelData.author
          }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { getPexelsFromId } from '@/api/pexels'
import { isMobileTerminal } from '@/utils/flexible'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'

const props = defineProps({
  id: {
    type: String,
    required: true
  }
})

const pexelData = ref({})
/**
 * 获取详情数据
 */
const getPexelData = async () => {
  const data = await getPexelsFromId(props.id)
  pexelData.value = data
}
getPexelData()

/**
 * 关闭按钮处理事件
 */
const router = useRouter()
const store = useStore()
const onPop = () => {
  // 配置跳转方式
  store.commit('app/changeRouterType', 'back')
  router.back()
}
</script>

<style lang="scss" scoped></style>

10: 处理刷新空白问题 

问题:例如 xx.xxx.xx/pins/5313576 这样的路径刷新浏览器时,会显示空白页面。

原因:项目中该路径未配置路由。

// src/views/pins/index.vue

<template>
  <div class="w-full h-full bg-zinc-200 dark:bg-zinc-800">
    <pins-vue :id="$route.params.id" />
  </div>
</template>

<script setup>
import pinsVue from './components/pins.vue'
</script>
// src/router/modules/mobile-routes.js

export default [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/main/index.vue')
  },
  {
    path: '/pins/:id',
    name: 'pins',
    component: () => import('@/views/pins/index.vue')
  }
]
// src/router/modules/pc-routes.js

export default [
  {
    path: '/',
    name: 'main',
    component: () => import('@/views/layout/index.vue'),
    children: [
      {
        path: '',
        name: 'home',
        component: () => import('@/views/main/index.vue')
      },
      {
        path: '/pins/:id',
        name: 'pins',
        component: () => import('@/views/pins/index.vue')
      }
    ]
  }
]

11: 总结 

        本篇文章中咱们处理了详情页面,在详情页面的处理中,我们通过另外一种方式完成了 路由的过渡行为。同时也接触到了 GSAP 这样的动画库,可以使我们的动画处理变的更加方便。

        接下来我们将要处理 登录、注册,大家拭目以待吧。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1690804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

202206青少年软件编程(Python)等级考试试卷(三级)

第 1 题 【单选题】 下图所示, 有一个名为"书目.csv"的文件。 小明针对这个文件编写了 5 行代码,请问, 代码运行到最后打印在屏幕上的结果是? ( ) with open(书目.csv, r, encoding=utf-8) as f:for line in f.readlines

Rabbitmq-Windows 安装

第一步&#xff1a;下载并安装erlang &#xff11;.原因&#xff1a;RabbitMQ服务端代码是使用并发式语言Erlang编写的&#xff0c;安装Rabbit MQ的前提是安装Erlang &#xff12;.下载地址&#xff1a;http://www.erlang.org/downloads 3.双击&#xff0c;点next就可以 4.选…

【网络技术】【Kali Linux】Wireshark嗅探(十四)QUIC(快速UDP互联网连接)协议报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客&#xff1a; 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;一&#xff09;ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;二&#xff09;TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

【Unity2D:Animator】为角色添加动画效果

一、添加Animator组件并创建Animator Controller文件 1. 添加Animator组件&#xff1a; 2. 在Assets-Art文件夹中新建一个名为Animations的文件夹&#xff0c;用来存储所有动画资源 3. 在Animations文件夹中新建一个名为Player的文件夹&#xff0c;再创建一个名为Animators的文…

2024电工杯数学建模A题Matlab代码+结果表数据教学

2024电工杯A题保姆级分析完整思路代码数据教学 A题题目&#xff1a;园区微电网风光储协调优化配置 以下仅展示部分&#xff0c;完整版看文末的文章 %A_1_1_A % 清除工作区 clear;clc;close all;warning off; %读取参数%正常读取 % P_LOADxlsread(附件1&#xff1a;各园区典…

springboot vue 开源 会员收银系统 (2) 搭建基础框架

前言 完整版演示 前面我们对会员系统https://blog.csdn.net/qq_35238367/article/details/126174288进行了分析 确定了技术选型 和基本的模块 下面我们将从 springboot脚手架开发一套收银系统 使用脚手架的好处 不用编写基础的rabc权限系统将工作量回归业务本身生成代码 便于…

盲人社会适应性训练:打开生活的新篇章

在现代社会的快节奏中&#xff0c;每一位成员都在寻求更好的方式来适应环境&#xff0c;对于盲人群体而言&#xff0c;这种适应性尤为关键。盲人社会适应性训练作为一个旨在提升盲人生活质量和独立性的系统性过程&#xff0c;正逐步受到广泛关注。在这一过程中&#xff0c;一款…

Jmeter+prometheus+grafana性能测试

文章目录 Jmeterprometheusgrafana性能测试背景目标设计思路原理案例启发 Jmeterprometheusgrafana性能测试 背景 ​ 在现代社会中&#xff0c;人们对于应用程序的响应速度和性能体验提出了越来越高的要求。无论是电子商务网站、社交媒体平台还是企业级软件系统&#xff0c;都…

Playwright教程

Playwright简介 支持多数浏览器 在Chromium&#xff0c;Firefox和WebKit上进行测试。Playwright拥有适用于所有现代浏览器的完整API覆盖&#xff0c;包括Google Chrome和Microsoft Edge&#xff08;带有Chromium&#xff09;&#xff0c;Apple Safari&#xff08;带有WebKit&a…

VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION--论文笔记

论文笔记 论文来源 Very Deep Convolutional Networks for Large-Scale Image Recognition 代码来源 还没上传 数据集 这里采用的是猫狗数据集 还没上传 1论文摘要的翻译 在这项工作中&#xff0c;我们研究了卷积网络深度对其在大规模图像识别设置中的准确性的影响。我…

CTFHUB技能树——SSRF(二)

目录 上传文件 ​FastCGI协议 Redis协议 上传文件 题目描述&#xff1a;这次需要上传一个文件到flag.php了.祝你好运 index.php与上题一样&#xff0c;使用POST请求的方法向flag.php传递参数 //flag.php页面源码 <?phperror_reporting(0);if($_SERVER["REMOTE_ADDR&…

删除MySQL中所有表的外键

方法一&#xff1a; 原理 查询schema中所有外键名称然后拼接生成删除语句 第一步&#xff1a; SELECT CONCAT(ALTER TABLE ,TABLE_SCHEMA,.,TABLE_NAME, DROP FOREIGN KEY ,CONSTRAINT_NAME, ;) FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA数据库名…

node环境问题(无法加载文件D:\Software\Node.js\node_global\vue.ps1,因为在此系统上禁止运行脚本。)

问题&#xff1a;npm安装lerna显示安装成功&#xff0c;但是lerna -v的时候报错 解决步骤&#xff1a; 1、输入&#xff1a;Get-ExecutionPolicy 2、输入&#xff1a;Set-ExecutionPolicy -Scope CurrentUser&#xff08;有选项的选Y&#xff09; 3、输入&#xff1a;RemoteSi…

超详细的前后端实战项目(Spring系列加上vue3)前端篇(二)(一步步实现+源码)

好了&#xff0c;兄弟们&#xff0c;继昨天的项目之后&#xff0c;开始继续敲前端代码&#xff0c;完成前端部分 昨天完成了全局页面的代码&#xff0c;和登录页面的代码&#xff0c;不过昨天的代码还有一些需要补充的&#xff0c;这里添加一下 内容补充&#xff1a;在调用登…

信息系统项目管理师0129:输入(8项目整合管理—8.7监控项目工作—8.7.1输入)

点击查看专栏目录 文章目录 8.7 监控项目工作8.7.1 输入8.7 监控项目工作 监控项目工作是跟踪、审查和报告整体项目进展,以实现项目管理计划中确定的绩效目标的过程。本过程的主要作用: 让干系人了解项目的当前状态并认可为处理绩效问题而采取的行动;通过成本和进度预测,让…

房地产支持政策加码不断,美克家居全力变革未来可期

2023年我国经济处于恢复发展阶段&#xff0c;而家具制造业“回温”速度明显慢于经济增速&#xff0c;在这一背景下&#xff0c;美克家居如此营收表现并不令人感到意外。而在充沛现金流支撑下&#xff0c;辅以全方位开展降本增效的年度经营规划&#xff0c;公司亏损收窄或已为期…

rclone迁移对象存储之间的数据

1 概述 rclone是一款文件复制工具&#xff0c;既可以用于在linux主机之间复制文件&#xff0c;也可以在对象存储之间复制文件。 rclone的官网为&#xff1a; https://rclone.orgrlcone关于对象存储的官方文档为&#xff1a; https://rclone.org/s32 安装 2.1 yum安装 yum …

【全网最全】2024电工杯数学建模A题前两问完整解答+21页初步参考论文+py代码+保奖思路等(后续会更新成品论文)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片链接&#xff0c;那是获取资料的入口&#xff01; 【全网最全】2024电工杯数学建模A题前两问完整解答21页初步参考论文py代码保奖思路等&#xff08;后续会更新成品论文&#xff09;「首先来看看目前已有…

在线生成数据库er图的工具

网址 https://databasediagram.com/ 其实很早之前我也有类似的想法&#xff0c;根据数据表结构&#xff0c;显示数据表之间的关系图。 当时我还写了一个工具&#xff0c;可惜后来就没怎么用过了。 这个网站和我当时的思路很像&#xff0c;只不过他这个页面显示比我的好得多&…

谈恋爱没经验?那就来刷谈恋爱经验宝宝吧

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…