Vue实现Tab标签路由效果并用Animate.css做转场动画效果的代码第1/3页

类似于浏览器窗口一样的路由切换逻辑,看着还是挺高大上的,本以为有很多高级的玩意儿,奈何复杂的东西总是由简单的东西拼接而成的,这个功能也不例外。

本篇文章主要描述两个问题:

如何实现这种Tab标签页的路由效果 如何为路由切换添加转场动画。

该功能的开发主要使用到 AntDesignVue 组件库的Tab组件和 Animate.css

效果如下:

Tab标签页实现

首先是该组件的模板部分, ContextMenu 组件是我们自定义的右键菜单,后面会说到。 a-tabs 组件则是 ant 的组件,具体用法不详述,可以查看官方文档。还有一个 PageToggleTransition 组件,是我们用来实现动画切换的组件,非常简单。

/**
 * TabLayout.vue 的模板部分,简单看一下有个印象
 */
<template>
 <PageLayout>
 <ContextMenu
 :list="menuItems"
 :visible.sync="menuVisible"
 @select="onMenuSelect"
 />
 <!-- 标签部分 -->
 <a-tabs
 type="editable-card"
 :hide-add="true"
 :active-key="activePage"
 @change="changePage"
 @edit="editPage"
 @contextmenu="onContextmenu"
 >
 <a-tab-pane v-for="page in pageList" :key="page.fullPath">
 <template #tab>
  <span :data-key="page.fullPath">
  {{ page.name }}
  </span>
 </template>
 </a-tab-pane>
 </a-tabs>
 <!-- 路由出口 -->
 <PageToggleTransition name="fadeIn">
 <keep-alive :exclude="dustbin">
 <router-view />
 </keep-alive>
 </PageToggleTransition>
 </PageLayout>
</template>

原理

维护一个 pageList ,通过监听路由变化动态的添加和删除page。而所谓的page,就是页面的路由对象($route),我们正是通过 $route.fullPath 作为页面的唯一标识的。而删除页面时不光要操作 pageList ,还要利用 keep-alive 组件的 exclude 属性删除缓存。至于 a-tabs 组件的这个插槽,主要是为了绑定一个数据key,以便触发contextmenu事件时,可以更容易的获取到对应页面的key值(fullPath)

理论存在,实践开始。

路由监听

watch: {
 $route: {
 handler (route) {
 this.activePage = route.fullPath
 this.putCache(route)
 const index = this.pageList.findIndex(item => item.fullPath === route.fullPath)
 if (index === -1) {
  this.pageList.push(route)
 }
 },
 immediate: true
 }
}

路由变化时,主要做三件事:

  • 设置当前页(activePage)
  • 将当前页加入缓存,即移出垃圾桶(dustbin)
  • 如果当前页不在pageList中,则添加进来。

页面跳转

methods: {
 changePage (key) {
 this.activePage = key
 this.$router.push(key)
 },
 editPage (key, action) {
 if (action === 'remove') {
 this.remove(key)
 }
 },
 remove (key) {
 if (this.pageList.length <= 1) {
 return message.info('最后一页了哦~')
 }
 let curIndex = this.pageList.findIndex(item => item.fullPath === key)
 const { matched } = this.pageList[curIndex]
 const componentName = last(matched).components.default.name
 this.dustbin.push(componentName)
 this.pageList.splice(curIndex, 1)
 // 如果删除的是当前页才需要跳转
 if (key === this.activePage) {
 // 判断向左跳还是向右跳
 curIndex = curIndex >= this.pageList.length ? this.pageList.length - 1 : curIndex
 const page = this.pageList[curIndex]
 this.$router.push(page.fullPath).finally(() => {
  this.dustbin.splice(0) // 重置,否则会影响到某些组件的缓存
 })
 }
 }
 ...
 ...
}

这里主要主要说一下remove方法:

  • 如果是最后一页,则忽略
  • 在pageList中找到当前页对应的组件名用于删除缓存(这里不清楚的可以看一下 keep-alive组件 ,和 $route.matched )
  • 如果删除的是当前页,需要进行页面跳转,向左挑还是向右跳呢?

需要强调的时 keep-aliveexclude 属性,当组件名被匹配到的时候就会立即清除缓存,所以, dustbin 添加完之后记得要重置,否则下次就不会缓存了。

自定义contextmenu事件

解释下,contextmenu事件就是右键菜单事件,我们可以通过监听事件,使得右键菜单事件触发的时候显示我们的自定义菜单。

methods: {
 // 自定义右键菜单的关闭功能
 onContextmenu (e) {
 const key = getTabKey(e.target) // 这里的判断,用到了前面在span标签上加的data-key自定义属性
 if (!key) return // 主要是为了控制菜单的显示或隐藏

 e.preventDefault() // 组织默认行为,显示我们的自定义邮件菜单
 this.menuVisible = true
 }
 ...
 ...
}
/**
 * 由于ant-design-vue组件库的TabPane组件暂不支持自定义监听器,无法直接获取到右键target所在标签页的key 。故增加此方法用于
 * 查询右键target所在标签页的标识 key ,以用于自定义右键菜单的事件处理。
 * 注:TabPane组件支持自定义监听器后可去除该方法并重构 ‘自定义右键菜单的事件处理'
 * @param target 查询开始目标
 * @param depth 查询层级深度 (查找层级最多不超过3层,超过3层深度直接返回 null)
 * @returns {String}
 */
function getTabKey (target, depth = 0) {
 if (depth > 2 || !target) {
 return null
 }
 return target.dataset.key || getTabKey(target.firstElementChild, ++depth)
}

另外要说的是,dom元素上以 data- 开头的属性会被收录进元素的 dataset 属性中, data-key 访问时就是 dom.dataset.key

下面就是我们的 ContextMenu 组件了:

效果图:

代码如下:

<template>
 <a-menu
 v-show="visible"
 class="contextmenu"
 :style="style"
 :selectedKeys="selectedKeys"
 @click="handleClick"
 >
 <a-menu-item v-for="item in list" :key="item.key">
 <a-icon v-if="item.icon" :type="item.icon"/>
 <span>{{ item.text }}</span>
 </a-menu-item>
 </a-menu>
</template>

<script>
export default {
 name: 'ContextMenu',
 props: {
 visible: {
 type: Boolean,
 required: false,
 default: false
 },
 list: {
 type: Array,
 required: true,
 default: () => []
 }
 },
 data () {
 return {
 left: 0,
 top: 0,
 target: null,
 selectedKeys: []
 }
 },
 computed: {
 style () {
 return {
 left: this.left + 'px',
 top: this.top + 'px'
 }
 }
 },
 created () {
 const clickHandler = () => this.closeMenu()
 const contextMenuHandler = e => this.setPosition(e)
 window.addEventListener('click', clickHandler)
 window.addEventListener('contextmenu', contextMenuHandler)
 this.$emit('hook:beforeDestroy', () => {
 window.removeEventListener('click', clickHandler)
 window.removeEventListener('contextmenu', contextMenuHandler)
 })
 },
 methods: {
 closeMenu () {
 this.$emit('update:visible', false)
 },
 setPosition (e) {
 this.left = e.clientX
 this.top = e.clientY
 this.target = e.target
 },
 handleClick ({ key }) {
 this.$emit('select', key, this.target)
 this.closeMenu()
 }
 }
}
</script>

<style lang="stylus" scoped>
 .contextmenu
 position fixed
 z-index 1000
 border-radius 4px
 border 1px lightgrey solid
 box-shadow 4px 4px 10px lightgrey !important
 .ant-menu-item
 margin 0 !important
</style>

这里需要强调的是钩子函数 created 的内容:

1.首先全局事件需要成对出现,有添加就要有移除,否则可能造成内存泄漏,并导致一些其他的bug。就比如在模块热替换的项目中,会造成反复绑定的问题。

2.为什么这里要给window绑定contextmenu事件和click事件,之前不是绑过了吗?这里的click事件主要是为了关闭菜单,右键菜单的特点是,不论点了什么点了哪里,只要点一下就会关闭。这里的contextmenu事件主要是为了获取到事件对象 event ,以此来设置菜单的位置。而之前绑定在 a-tabs 组件上的contextmenu事件主要是为了阻止默认事件,我们只拦截了该组件,而不需要拦截全局范围。

自定义右键菜单主要是为了 从 event.target 中获取到我们需要的key并以事件的形式传递出来 ,便于分发后面的逻辑,即:

onMenuSelect (key, target) {
 const tabKey = getTabKey(target)
 switch (key) {
 case '1': this.closeLeft(tabKey); break
 case '2': this.closeRight(tabKey); break
 case '3': this.closeOthers(tabKey); break
 default: break
 }
}

这三种情况的逻辑是基本一致的,主要做了三件事:

  1. 清除缓存
  2. 删除页面,并设置当前页面
  3. 页面跳转

以closeOthers为例:

closeOthers (tabKey) {
 const index = this.pageList.findIndex(item => item.fullPath === tabKey) // 找到触发事件时鼠标停留在那个tab上
 for (const route of this.pageList) {
 if (route.fullPath !== tabKey) {
  this.clearCache(route) // 清缓存
 }
 }
 const page = this.pageList[index]
 this.pageList =  // 设置pageList,这里清除其他,也就是保留自己
 this.activePage = page.fullPath
 this.$router.push(this.activePage).catch(e => e)
}

缓存控制

这部分逻辑比较简单,结合注释可以看懂

methods: {
 clearCache (route) {
 const componentName = last(route.matched).components.default.name // last方法来自lodash,获取数组最后一个元素
 this.dustbin.push(componentName) // 清除
 },
 putCache (route) {
 const componentName = last(route.matched).components.default.name
 if (this.dustbin.includes(componentName)) {
  this.dustbin = this.dustbin.filter(item => item !== componentName) // 从dustbin中删除当前组件名,恢复其缓存机制
 }
 }
}

这样,主要逻辑就做完了,下面简单说说转场动画的实现

转场动画实现

转场动画主要是用到 Animate.css 配合Vue的 transition 组件实现,组件完整代码如下,极其简单:

<template>
 <transition :enter-active-class="`animate__animated animate__${name}`">
 <slot />
 </transition>
</template>

<script>
export default {
 name: 'PageToggleTransition',
 props: {
 name: String
 }
}
</script>

具体参考官方文档 关于transition组件的说明

最后 借鉴自 vue-antd-admin github.com/1446445040/…

完整代码

<template>
 <PageLayout>
 <ContextMenu
 :list="menuItems"
 :visible.sync="menuVisible"
 @select="onMenuSelect"
 />
 <!-- 标签部分 -->
 <a-tabs
 type="editable-card"
 :hide-add="true"
 :active-key="activePage"
 @change="changePage"
 @edit="editPage"
 @contextmenu="onContextmenu"
 >
 <a-tab-pane v-for="page in pageList" :key="page.fullPath">
 <template #tab>
  <span :data-key="page.fullPath">
  {{ page.name }}
  </span>
 </template>
 </a-tab-pane>
 </a-tabs>
 <!-- 路由出口 -->
 <PageToggleTransition name="fadeIn">
 <keep-alive :exclude="dustbin">
 <router-view />
 </keep-alive>
 </PageToggleTransition>
 </PageLayout>
</template>

<script>
import { message } from 'ant-design-vue'
import { last } from 'lodash'
import PageLayout from './PageLayout'
import ContextMenu from '../components/common/ContextMenu'
import PageToggleTransition from '../components/transition/PageToggleTransition'

export default {
 name: 'TabLayout',
 components: { PageToggleTransition, ContextMenu, PageLayout },
 data () {
 return {
 pageList: [],
 dustbin: [],
 activePage: '',
 menuVisible: false,
 menuItems: [
 { key: '1', icon: 'arrow-left', text: '关闭左侧' },
 { key: '2', icon: 'arrow-right', text: '关闭右侧' },
 { key: '3', icon: 'close', text: '关闭其它' }
 ]
 }
 },
 watch: {
 $route: {
 handler (route) {
 this.activePage = route.fullPath
 this.putCache(route)
 const index = this.pageList.findIndex(item => item.fullPath === route.fullPath)
 if (index === -1) {
  this.pageList.push(route)
 }
 },
 immediate: true
 }
 },
 methods: {
 changePage (key) {
 this.activePage = key
 this.$router.push(key)
 },
 editPage (key, action) {
 if (action === 'remove') {
 this.remove(key)
 }
 },
 remove (key) {
 if (this.pageList.length <= 1) {
 return message.info('最后一页了哦~')
 }
 let curIndex = this.pageList.findIndex(item => item.fullPath === key)
 const { matched } = this.pageList[curIndex]
 const componentName = last(matched).components.default.name
 this.dustbin.push(componentName)
 this.pageList.splice(curIndex, 1)
 // 如果删除的是当前页才需要跳转
 if (key === this.activePage) {
 // 判断向左跳还是向右跳
 curIndex = curIndex >= this.pageList.length ? this.pageList.length - 1 : curIndex
 const page = this.pageList[curIndex]
 this.$router.push(page.fullPath).finally(() => {
  this.dustbin.splice(0) // 重置,否则会影响到某些组件的缓存
 })
 }
 },
 // 自定义右键菜单的关闭功能
 onContextmenu (e) {
 const key = getTabKey(e.target)
 if (!key) return

 e.preventDefault()
 this.menuVisible = true
 },
 onMenuSelect (key, target) {
 const tabKey = getTabKey(target)
 switch (key) {
 case '1': this.closeLeft(tabKey); break
 case '2': this.closeRight(tabKey); break
 case '3': this.closeOthers(tabKey); break
 default: break
 }
 },
 closeOthers (tabKey) {
 const index = this.pageList.findIndex(item => item.fullPath === tabKey)
 for (const route of this.pageList) {
 if (route.fullPath !== tabKey) {
  this.clearCache(route)
 }
 }
 const page = this.pageList[index]
 this.pageList =
 this.activePage = page.fullPath
 this.$router.push(this.activePage).catch(e => e)
 },
 closeLeft (tabKey) {
 const index = this.pageList.findIndex(item => item.fullPath === tabKey)
 this.pageList.forEach((route, i) => {
 if (i < index) {
  this.clearCache(route)
 }
 })
 const restPages = this.pageList.slice(index)
 this.pageList = restPages
 // 判断当前activePage是否在将要删除的页面中
 const curActivePage = restPages.find(item => item.fullPath === this.activePage)
 if (!curActivePage) {
 this.activePage = restPages[0].fullPath
 this.$router.push(this.activePage).catch(e => e)
 }
 },
 closeRight (tabKey) {
 const index = this.pageList.findIndex(item => item.fullPath === tabKey)
 this.pageList.forEach((route, i) => {
 if (i > index) {
  this.clearCache(route)
 }
 })
 const restPages = this.pageList.slice(0, index + 1)
 this.pageList = restPages
 // 判断当前activePage是否在将要删除的页面中
 const curActivePage = restPages.find(item => item.fullPath === this.activePage)
 if (!curActivePage) {
 this.activePage = last(restPages).fullPath
 this.$router.push(this.activePage).catch(e => e)
 }
 },
 /**
 * 缓存控制
 */
 clearCache (route) {
 const componentName = last(route.matched).components.default.name
 this.dustbin.push(componentName) // 清除
 },
 putCache (route) {
 const componentName = last(route.matched).components.default.name
 if (this.dustbin.includes(componentName)) {
 this.dustbin = this.dustbin.filter(item => item !== componentName)
 }
 }
 }
}

/**
 * 获取Tab标签下dom节点中自定义的数据,递归向下查找最多3层(观察Tab组件渲染后的DOM得出)
 * 该方式属于hack手段,不得已为之
 * @param{HTMLElement} target event.target
 * @param depth 深度
 */
function getTabKey (target, depth = 0) {
 if (depth > 2 || !target) {
 return null
 }
 return target.dataset.key || getTabKey(target.firstElementChild, ++depth)
}
</script>

总结

到此这篇关于Vue实现Tab标签路由效果,并用Animate.css做转场动画的文章就介绍到这了,更多相关Vue实现Tab标签路由内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解vue2.0 使用动态组件实现 Tab 标签页切换效果(vue-cli)

    在 vue 中,实现 Tab 切换主要有三种方式:使用动态组件,使用 vue-router 路由,使用第三方插件. 因为这次完成的功能只是简单切换组件,再则觉得使用路由切换需要改变地址略微麻烦,所以使用的是动态组件实现,如果是在大型应用上,可能使用 vue-router 会方便一些. 先看下最终实现的效果,结构比较简单,顶部的三个 Tab 标签用于切换,内容区域分别为三个子组件. 效果预览 关键代码及分析如下: <template> // 每一个 tab 绑定了一个点击事件,传入的参数对应着

  • vue-router实现tab标签页(单页面)详解

    vue-router 是 Vue.js 官方的路由插件,适合用于构建标签页应用.Vue 的标签页应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来,vue-router 会把各个组件渲染到正确的地方. 首先,.vue中的内容非常简单,<router-link>创建标签,并指定路径,<router-view>渲染路由匹配到的组件. <template> <div id="account"> <p class=&quo

  • Vue2.0 给Tab标签页和页面切换过渡添加样式的方法

    使用vue-router构建单页应用时,如何给当前页面的标签页添加样式呢? 1.在app.vue文件中你的nav中添加路由地址 2.依然在app.vue文件中,添加样式 不用在任何位置加class,只要在css中写上这个名称,样式自己随意改. 如何在单页切换的时候添加过渡? 1.在app.vue文件中,将router-view包裹在transition中,transition加个name. 2.在app.vue中添加css样式(你的transition的name是什么,第一个单词就是什么) 以上

  • vue自定义标签和单页面多路由的实现代码

    1. 自定义组件标签(如在主页插入顶栏/侧边栏等)   比如说要将Header.vue插入Home.vue中显示:定义好Header.vue,然后在Home.vue的script中导入Header.vue: import vHead from "./Header.vue"; #导入Header.vue为vHead,注意路径, Header.vue和Home.vue 在同一路径下用./ 然后导出组件: export default { components: { vHead, } };

  • vuex + keep-alive实现tab标签页面缓存功能

    在开发很多管理系统过程之中,常遇到这种需求,需要对打开路由页面进行缓存,然后在系统页眉提供方便查阅的tab标签进行切换以及对已经缓存页面进行数据刷新和清除数据操作.具体演示如下图所示: 在上面演示中实现了类似 window tab 标签页效果,会对当前数据进行缓存.在浏览器中实现对路由页面的缓存可以减少接口请求,也方便了用户来回切换想搜索的数据列表. 原理 Vue 提供的 keep-alive API实现对路由组件的缓存. include 属性可以绑定一个数组,里面是需要路由组件的 name 值

  • Vue实现Tab标签路由效果并用Animate.css做转场动画效果的代码第1/3页

    类似于浏览器窗口一样的路由切换逻辑,看着还是挺高大上的,本以为有很多高级的玩意儿,奈何复杂的东西总是由简单的东西拼接而成的,这个功能也不例外. 本篇文章主要描述两个问题: 如何实现这种Tab标签页的路由效果 如何为路由切换添加转场动画. 该功能的开发主要使用到 AntDesignVue 组件库的Tab组件和 Animate.css 效果如下: Tab标签页实现 首先是该组件的模板部分, ContextMenu 组件是我们自定义的右键菜单,后面会说到. a-tabs 组件则是 ant 的组件,具体

  • vue中使用animate.css实现炫酷动画效果

    目录 1.安装(在vscode终端中,使用npm安装) 2.引入 3.代码实现 animate.css 是一个来自国外的 CSS3 动画库,它提供了抖动(shake).闪烁(flash).弹跳(bounce).翻转(flip).旋转(rotateIn/rotateOut).淡入淡出(fadeIn/fadeOut)等多达 60 多种动画效果,几乎包含了所有常见的动画效果.这些效果在大多数支持CSS3的浏览器上都能保持一致.简单来说,我们使用它,只需要写很少的代码,就可以实现非常炫酷的动画效果. 官

  • vue实现tab标签(标签超出自动滚动)

    当创建的tab标签超出页面可视区域时自动滚动一个tab标签距离,并可手动点击滚动tab标签,实现效果请看GIF图 效果预览GIF图 <template> <div class="main-box"> <button @click="add">添加</button> <div class="main-box-tab"> <i @click="previous"&g

  • vue router自动判断左右翻页转场动画效果

    前段时间做了一个移动端spa项目,技术基于 :vue + vue-router + vuex + mint-ui 因为使用了vue-cli脚手架的webpack模版,所有页面都以.vue为后缀的文件作为一个组件 最近公司项目比较少终于有空来记录一下自己对vue-router的一些小小的使用心得, 一般的移动端口单页应用在跳转页面时候会有相应的转场动画,比如: 1. 从当前一级页面跳转二级页面需要展示的转场动画是一级页面向屏幕左边移动消失的同时, 二级页面从屏幕的右边向左边移动出现.(类似翻书翻到

  • vue+vue-router转场动画的实例代码

    Vue+WebPack+HBuilder 项目记录 项目搭建完毕了,但是由于是单页应用嵌入HBuilder的时候无法利用它的转场动画,于是找到了vue的转场动画写法,使体验与APP靠近,在此记录: 1.首先我们要监听路由然后判断其是前进还是后退,来实现不同的动画 export default { name: 'app', data () { return { transitionName: 'slide-left' } }, watch: { '$route' (to, from) { cons

  • Android Flutter实现页面切换转场动画效果

    目录 前言 Hero 动画过程 Hero 基础示例 总结 前言 写了一篇基础的性能优化的内容,继续我们的动画相关的介绍.今天的主角是英雄 —— Hero 组件.Hero 组件非常适合从列表.概览页切换到详情页转场动画场合.因为可以将两个页面的组件串起来动画,体验上会觉得整个操作的连贯性非常好.下面是我们这篇要做的一个效果. 屏幕录制2021-11-09 下午9.39.49.gif Hero 动画过程 Hero 本质是是在不同的路由页面做了一个中转层,然后通过动画完成过渡,下面用4张图是官方演示的

  • WPF实现倒计时转场动画效果

    代码如下 一.创建 CountdownTimer.xaml 继承ContentControl代码如下. using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windo

  • angular2系列之路由转场动画的示例代码

    Angular2的动画系统赋予了制作各种动画效果的能力,致力于构建出与原生CSS动画性能相同的动画. Angular2的动画主要是和@Component结合在了一起. animations元数据属性在定义@Component装饰.就像template元数据属性!这样就可以让动画逻辑与其应用代码紧紧集成在一起,这让动画可以更容易的出发与控制. 一.在app.mudule.ts中引入: import { BrowserAnimationsModule } from '@angular/platfor

  • jQuery的animate函数实现图文切换动画效果

    在一些图片网站上我们可以看到在展示图片的时候,用鼠标轻轻滑上图片可以看到该图片的文字介绍信息,其实用jQuery的animate函数就可以实现这样一个动画过程. <div class="wrap"> <img src="images/s1.jpg" alt="photo" /> <div class="cover"> <h3>强震摧毁加勒比海小国海地</h3> <

  • 不用图片作背景CSS做的小灯笼效果_练习用

    写这个效果,可以熟悉以下: 1.相对定位中的绝对定位: 2.CSS 针对浏览器 HACK: 3.了解几种边框,以及可以实现的变化:以及加了边框后,宽度和高度的计算方法: 4.感受浮动,浮动的东西只有多作一些,才会感受更深一些: 5.还可以熟悉一下小学的加减法:呵-- 很早以前我都是拿计算器在写CSS. 首先,我先用FW画一个小灯笼,在画的过程中,我尽可能不出现圆角,并画成等宽,等高.居中:因为这些属性在CSS中都有!文字12号加粗! 于是,开始想布局!这个布局太麻烦,我不得不用更多的标签来完成,

随机推荐