当前位置: 首页 > news >正文

移动端h5路由过渡动画实现教程

Vue 3 + Vue Router 路由过渡动画:前进 / 返回区分

在 H5 或 App 内嵌页里,我们常希望:

  • 前进(点链接、按钮):新页面从右侧滑入,当前页略微左移(类似 iOS 右推)。
  • 返回(NavBar 返回、浏览器/微信后退):当前页向右滑出,上一页露出。

Vue Router 本身不区分「这次导航是用户点了返回还是点了链接」,浏览器后退又依赖 popstate,触发时机可能晚于路由更新,导致动画方向错乱。下面是一套可复用的实现方式。


一、效果与思路

操作 期望动画
点击进入下一页 新页从右滑入(slide-cover)
NavBar / 浏览器返回 当前页向右滑出,上一页露出(slide-back)

做法:用一个全局的「方向」状态(forward / back),在每次导航前根据「是谁触发的」设好方向,再在根组件的 <transition :name="..."> 里根据方向选不同动画名和 CSS。


二、用 Pinia 存「过渡方向」

// stores/transition.js
import { defineStore } from 'pinia'export const useTransitionStore = defineStore('transition', {state: () => ({ direction: 'forward' }),actions: {setForward() {this.direction = 'forward'},setBack() {this.direction = 'back'},},
})

默认 forward,只在「确定是返回」时改成 back


三、根组件:按方向切换过渡名和样式

router-view + 单层 transition,根据 store 的 directionslide-coverslide-back

<!-- App.vue -->
<template><div class="wrapper"><router-view v-slot="{ Component }"><transition :name="transitionName"><component :is="Component" :key="$route.fullPath" class="page-layer" /></transition></router-view></div>
</template><script setup>
import { computed } from 'vue'
import { useTransitionStore } from '@/stores/transition'const transitionStore = useTransitionStore()
const transitionName = computed(() =>transitionStore.direction === 'back' ? 'slide-back' : 'slide-cover'
)
</script>

页面层需要铺满且可滚动,且两页能叠在一起做过渡,所以用绝对定位:

.wrapper {position: fixed;inset: 0;overflow: hidden;
}
.page-layer {position: absolute;inset: 0;width: 100%;height: 100%;overflow: auto;-webkit-overflow-scrolling: touch;
}

前进(slide-cover):新页从右边进来,旧页向左挪一点。

.slide-cover-enter-from { transform: translateX(100%); }
.slide-cover-enter-to   { transform: translateX(0); }
.slide-cover-leave-from { transform: translateX(0); }
.slide-cover-leave-to   { transform: translateX(-24%); }

返回(slide-back):当前页向右出去,上一页从左侧露出。

.slide-back-enter-from { transform: translateX(-24%); }
.slide-back-enter-to   { transform: translateX(0); }
.slide-back-leave-from { transform: translateX(0); }
.slide-back-leave-to   { transform: translateX(100%); }

记得给 enter-active / leave-active 设好 transitionz-index,让「进来的」在上层,避免穿帮。完整样式见文末附录。


四、在入口里统一设置「方向」

main.js 里做三件事:popstate 提前 setBackbeforeEach 兜底包装 push/back。且要在 import router 之前就挂好 popstate,这样浏览器后退时尽量先改方向再让路由更新。

1. 浏览器后退:监听 popstate(捕获阶段)

let transitionStoreRef = null
window.addEventListener('popstate', () => transitionStoreRef?.setBack(), true)import router from './router'

用第三个参数 true 进入捕获阶段,尽量在 Vue Router 自己的 popstate 之前执行,这样路由切过去时 direction 已经是 back

2. 创建 app 并拿到 store 引用

const app = createApp(App)
app.use(pinia)
app.use(router)transitionStoreRef = useTransitionStore()

3. beforeEach:路径变浅时兜底为「返回」

有些环境下 popstate 会晚于路由更新,第一次点浏览器后退时方向可能还是 forward。可以用「目标路径比当前路径浅」在 beforeEach 里再设一次 setBack() 作为兜底。

注意要排除从登录页离开的情况:登录 → 首页是「前进」,不是返回,所以 from.path === '/login' 时不要当成 back。

const pathDepth = (path) => path.split('/').filter(Boolean).length
router.beforeEach((to, from) => {if (pathDepth(to.path) < pathDepth(from.path) && from.path !== '/login') {transitionStoreRef.setBack()}
})

4. 包装 router.push / router.back

这样无论是 <router-link>router.push() 还是 NavBar 里调 router.back(),方向都会正确:

const originalPush = router.push
const originalBack = router.back
router.push = function (...args) {transitionStoreRef.setForward()return originalPush.apply(this, args)
}
router.back = function () {transitionStoreRef.setBack()return originalBack.apply(this, arguments)
}

五、使用约定

  • 所有「返回」都用 router.back()history.back(),不要用 router.push(上一页),否则会走成「前进」动画。
  • 登录页:若你设计成「登录才是第一页、从首页点去登录算退出」,则从登录页跳首页应保持「前进」;用 from.path !== '/login' 排除即可。若有其它类似「路径变浅但算前进」的页面,可以同样加例外条件。

六、小结

触发方式 如何设成 back / forward
点击链接 / 按钮 包装的 router.pushsetForward()
NavBar 返回 包装的 router.back()setBack()
浏览器 / 微信 返回 popstate(捕获)→ setBack(),必要时 beforeEach 按路径深度兜底并排除登录页

这样就能在 Vue 3 + Vue Router 4 下稳定做出「前进从右进、返回向右出」的页面过渡,并兼容浏览器后退和登录等特殊路由。


附录:完整过渡 CSS(含 z-index 与 duration)

.slide-cover-enter-active,
.slide-cover-leave-active {transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-cover-enter-active { z-index: 2; }
.slide-cover-enter-from { transform: translateX(100%); }
.slide-cover-enter-to { transform: translateX(0); }
.slide-cover-leave-active { z-index: 1; }
.slide-cover-leave-from { transform: translateX(0); }
.slide-cover-leave-to { transform: translateX(-24%); }.slide-back-enter-active,
.slide-back-leave-active {transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-back-enter-active { z-index: 1; }
.slide-back-enter-from { transform: translateX(-24%); }
.slide-back-enter-to { transform: translateX(0); }
.slide-back-leave-active { z-index: 2; }
.slide-back-leave-from { transform: translateX(0); }
.slide-back-leave-to { transform: translateX(100%); }
http://www.jsqmd.com/news/358899/

相关文章:

  • Error creating bean with name ‘ragController‘: Injection of resource dependencies failed
  • Doris并发控制机制:高并发查询的应对策略
  • 6343456345
  • 45545634
  • USACO历年黄金组真题解析 | 2003年3月
  • 2026 外卖省钱首选美团外卖 50 + 品牌大额券加持完胜其他平台 - Top品牌推荐
  • USACO历年黄金组真题解析 | 2003年11月
  • 2026年麒麟大口茶、益禾堂等12大品牌在哪点更便宜?美团更便宜!6.9元起+半价周末+春节专属福利,下单路径一文看懂 - Top品牌推荐
  • 2026年麒麟大口茶美团多重福利狂欢!6.9元起薅羊毛,认准美团点单更便宜 - Top品牌推荐
  • 效果-Stardust粒子
  • 学习进度 23
  • HBase集群部署指南:高可用大数据存储方案
  • 明星同款外卖点单攻略出炉!郭麒麟/陈赫/鹿晗/张元英同款,美团点单最划算+营销活动路径详解 - Top品牌推荐
  • 麦当劳怎么点才更便宜?美团外卖“半价周末”等系列活动帮你省钱攻略 - Top品牌推荐
  • WordPress中if语句判断字段是否存在并输出内容
  • 明星同款外卖点单攻略:美团外卖最划算,多重福利+清晰路径解锁同款美味 - Top品牌推荐
  • [英语基础]形容词/副词
  • embedding模型对比分析——paraphrase-multilingual-MiniLM-L12-v2与bge-embedding
  • 注册中心宕机后,RPC调用还能成功吗?主流框架实测级分析
  • 明星同款外卖点单攻略:美团外卖解锁最划算路径,多重福利叠加更省钱 - Top品牌推荐
  • GitHub Pages 技术文档站点搭建实践指南
  • WPF CommunityToolkit.mvvm implement dependency injection via ServiceBuilder and ServiceCollection
  • 首款AI截图软件哪个好用又免费?全能截图翻译录屏GIF神器一键长截图OCR贴图取色无广告小巧免登录
  • 【微服务 Day1】SpringCloud实战开发(Mybatis-plus + Docker) - 详解
  • 第三十六节:EFCore10.0新增功能和中断性变更
  • 个人网盘管理|基于springboot + vue个人网盘管理系统(源码+数据库+文档) - 实践
  • 三亚精选十大海鲜美食推荐,让你的味蕾一次满足
  • 4.2 缓存策略与多级缓存:如何减少90%的数据库访问?
  • 3.3 可用性测试与演练:如何验证系统在极端情况下的表现?
  • 电子元器件-保险丝的选项