用vue写轮子的一些心得(六)——nav导航栏组件
需求分析支持横向导航竖向导航;支持导航click展开下拉列表;支持下拉列表click展开二级下拉列表,可任意层级;配有展开动画;方法实现1、定义组件:在html中定义t-nav组件定义为最外层导航栏包裹器,nav-item组件包裹每一项导航名字,并且每个nav-item组件都声明一个name,用以标识每一项导航。sub-nav组件包裹含有下拉列表的导航项。<t-nav class="box"
需求分析
- 支持横向导航竖向导航;
- 支持导航click展开下拉列表;
- 支持下拉列表click展开二级下拉列表,可任意层级;
- 配有展开动画;
方法实现
1、定义组件:
在html中定义
t-nav组件定义为最外层导航栏包裹器,nav-item组件包裹每一项导航名字,并且每个nav-item组件都声明一个name,用以标识每一项导航。sub-nav组件包裹含有下拉列表的导航项。
<t-nav class="box" :selected.sync="selected" vertical style="width: 200px;">
<nav-item name="home">首页</nav-item>
<sub-nav name="about">
<template slot="title">关于</template>
<nav-item name="culture">企业文化</nav-item>
<nav-item name="developers">开发团队</nav-item>
<nav-item name="contacts">联系电话</nav-item>
<sub-nav name="phone">
<template slot="title">联系方式</template>
<nav-item name="cm">移动</nav-item>
<nav-item name="cu">联通</nav-item>
<nav-item name="cn">电信</nav-item>
</sub-nav>
</sub-nav>
<nav-item name="hire">招聘</nav-item>
</t-nav>
2、基础功能逻辑 :
t-nav组件定义slot插槽,主要负责nav-item和sub-nav组件的传递。vertical代表上面html中:selected的初始选中值。
<template>
<div class="nav-content" :class="{vertical}">
<slot></slot>
</div>
</template>
nav-item组件定义导航每一项,可点击,点击后选中值为当前项,并展示相关样式。
<template>
<div class="nav-item-content" :class="{selected}" @click="onClick">
<slot></slot>
</div>
</template>
sub-item组件定义一组下拉导航,可点击,根据横向导航或是竖向导航展示不同下拉状态。
<template>
<div class="sub-nav-content" :class="{active, vertical}" v-click-outside="close">
<span class="sub-nav-content-label" @click="onClick">
<slot name="title"></slot>
<span class="sub-nav-content-icon" :class="{open, vertical}">
<t-icon name="right"></t-icon>
</span>
</span>
<template v-if="vertical">
<transition @enter="enter" @leave="leave" @after-leave="afterLeave" @after-enter="afterEnter">
<div class="sub-nav-content-popover" v-show="open" :class="{vertical}">
<slot></slot>
</div>
</transition>
</template>
<template v-else>
<div class="sub-nav-content-popover" v-show="open">
<slot></slot>
</div>
</template>
</div>
</template>
3、支持横向导航竖向导航
我们在t-nav组件标签上定义 vertical,有vertical则表示竖向导航,无则表示横向导航。然后将vertical传给nav组件和sub-nav组件,通过判断更改组件内部flex排列样式即可,这里css反而比较重点也比较难写。
t-nav组件:
<t-nav class="box" :selected.sync="selected" vertical style="width: 200px;">
nav组件:
vertical是true则CSS展示flex-direction: column。
<template>
<div class="nav-content" :class="{vertical}">
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
@import "../../styles/var";
.nav-content {
display: flex;
border-bottom: 1px solid $grey;
color: $color;
cursor: default;
&.vertical {
flex-direction: column;
border: 1px solid $grey;
}
}
</style>
sub-nav组件:
通过vertical判断展示哪段html,即横向或是竖向,并展示相应CSS。
<style lang="scss" scoped>
@import '../../styles/var';
.sub-nav-content {
position: relative;
&:not(.vertical) {
&.active {
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
border-bottom: 2px solid $blue;
width: 100%;
}
}
}
}
</style>
4、支持导航click展开下拉列表
通过sub-nav下拉导航组件来展示,v-show="open" 是否显示下拉列表。为什么用v-show,而不是v-if。因为v-if从false到true的状态会触发生命周期钩子:created、updated。v-if并不是能一开始跟随父组件一起经历所有的钩子事件,而是单独从false状态到true状态时触发的。因此在数据传递的过程中并不是同步的,会出现BUG。
<template v-else>
<div class="sub-nav-content-popover" v-show="open">
<slot></slot>
</div>
</template>
5、支持下拉列表click展开二级下拉列表,可任意层级
原理是通过sub-nav组件与nav-item组件嵌套即可实现,但sub-nav下面的nav-item与t-nav下面的sub-nav不属于同级,层级比较深,相当于sub-nav下面的nav-item可能是层级很深的子孙组件,存在跨组件通讯的问题。
怎么解决这个问题?使用依赖注入provide/inject,将父级的数据注入到其底下所有的子子孙孙当中,这样无论子孙组件层级多深,我们都可以调用到父级的数据并操作。
<t-nav class="box" :selected.sync="selected" vertical style="width: 200px;">
<sub-nav name="about">
<sub-nav name="phone">
<template slot="title">联系方式</template>
<nav-item name="cm">移动</nav-item>
<nav-item name="cu">联通</nav-item>
<nav-item name="cn">电信</nav-item>
<sub-nav name="phone">
<template slot="title">联系方式</template>
<nav-item name="cm">移动</nav-item>
<nav-item name="cu">联通</nav-item>
<nav-item name="cn">电信</nav-item>
</sub-nav>
</sub-nav>
</sub-nav>
</t-nav>
父组件t-nav:
export default {
name: 't-nav',
provide() { //依赖
return {
root: this,
vertical: this.vertical
}
},
}
子孙组件nav-item:
export default {
name: 't-nav-item',
inject: ['root'], //注入
mounted() {
this.root.addItem(this)
},
}
子孙组件sub-nav:
export default {
inject: ['root', 'vertical'], //注入
computed: {
active() {
return this.root.namePath.indexOf(this.name) > -1
},
},
}
6、配有展开动画
下拉的展开动画,通过vue中的transiton动画组件即可完成。但这里我们特意用了transition的动画钩子实现的,就是定义动画开始、动画结束等等相关时调用钩子来做一些操作,这在写复杂的动画样式时非常有用。
<transition @enter="enter" @leave="leave" @after-leave="afterLeave" @after-enter="afterEnter">
<div class="sub-nav-content-popover" v-show="open" :class="{vertical}">
<slot></slot>
</div>
</transition>
methods: {
enter(el, done) {
let {height} = el.getBoundingClientRect()
el.style.height = 0
el.getBoundingClientRect()
el.style.height = `${height}px`
el.addEventListener('transitionend', ()=>{
done()
})
},
afterEnter(el) {
el.style.height = 'auto'
},
leave(el, done) {
let {height} = el.getBoundingClientRect()
el.style.height = `${height}px`
el.getBoundingClientRect()
el.style.height = 0
el.addEventListener('transitionend', ()=>{
done()
})
},
afterLeave(el) {
el.style.height = 'auto'
},
}
&-popover {
position: absolute;
top: 100%;
left: 0;
border: 1px solid black;
white-space: nowrap;
background: white;
margin-top: 4px;
box-shadow: 0 0 3px fade_out(black, 0.8);
border-radius: $border-radius;
font-size: $font-size;
color: $light-color;
min-width: 8em;
&.vertical {
position: static;
border-radius: 0;
border: none;
box-shadow: none;
transition: height .3s;
overflow: hidden;
}
}
这里钩子的使用时,要注意浏览器的对于样式的多次修改会执行合并操作,浏览器只会执行最后一个样式。所以当我们在短时间内要执行多个样式的时候,需要在样式中间做点动作,让浏览器无法合并样式操作。
比如:
el.style.height = `${height}px`
el.getBoundingClientRect()
el.style.height = 0
更多推荐
所有评论(0)