menu导航菜单组件预览地址javascript
<x-menu :selected.sync="selected" >
<x-sub-menu name="extension">
<template slot="title">扩展</template>
<x-menu-item name="mac">for Mac</x-menu-item>
<x-menu-item name="windows">for Windows</x-menu-item>
</x-sub-menu>
<x-sub-menu name="learn">
<template slot="title">如何使用</template>
<x-menu-item name="fast">快速入门</x-menu-item>
<x-menu-item name="advanced">进阶配置</x-menu-item>
<x-menu-item name="package">多语言支持</x-menu-item>
</x-menu>
复制代码
如图所示,关于(selected
:被选中的那个item),在menu
里面控制,经过watchChild
“监听每一个item”,一旦menu-item
被点击便会触发css
this.$emit('menuItemUpdate',this.name)
复制代码
这里menu
便会经过menu-item
的$emit
和$on
实现数据的传递html
watchChild(){
this.items.forEach(vm=>{
vm.$on('menuItemUpdate',name=>{
this.$emit('update:selected',[name])
})
})
},
复制代码
这里的this.items
就是收集的每个menu-item
,这在一开始就已经完成了。 要用到依赖注入
,这里面子组件menu-item
都直接操做menu
的data
,耦合度很是高。vue
//menu
provide(){
return {
root:this
}
}
复制代码
//menu-item
inject:['root'],
created(){
this.root.addItem(this)
//.........
},
复制代码
而后告诉menu-item
你能够被选中了。updated
钩子函数用做完成这个任务再适合不过了java
updated(){
this.updateChild()
},
methods:{
updateChild(){
this.items.forEach(vm=>{
if(this.selected.indexOf(vm.name)>-1){
vm.selected = true
}else{
vm.selected = false
}
})
}
}
复制代码
当menu-item
被选中时,触发tellParents
函数,收集这条路径上全部父元素的name
,把这个收集好的数据放到selectedArr
里面。这个还能够用来高亮路径上的父元素。 在click的时候触发onClick
函数git
onClick(){
//...........
let subFather = this.$parent.$el.classList.contains('x-sub-menu') this.$parent.$el.classList.contains('x-sub-menu')
let groupFather = this.$parent.$el.classList.contains('x-menu-item-group')
//..........
if(subFather||groupFather){
//.......
this.tellParents(this)
//.......
}else{
//.......
}
},
复制代码
tellParents
递归调用自身来收集 name
github
tellParents(that){
if(that.$parent.$parent.$options.name==='x-sub-menu' ||that.$parent.$parent.$options.name==='x-menu-item-group'){
this.root.selectedArr.unshift(that.$parent.name)
this.tellParents(that.$parent)
}else{
this.root.selectedArr.unshift(that.$parent.name)
console.log(this.root.selectedArr)
}
},
复制代码
最后就是我但愿在menu-item
被选中后,能够关闭路径的全部弹出框popover。 这一样须要一个递归遍历windows
childClosePopover(){
if(this.$parent.$options.name==='x-sub-menu'){
this.open = false
this.$parent.childClosePopover()
}
},
复制代码
参照了一下Menu Attribute。 这里的文字颜色和图标颜色是同步的, hover的css效果和active
的同样bash
如图所示的紫色的路线,经过递归遍历通知menu树
里面的每个分支,改变颜色,是否垂直,disabled等等。 大部分添加的功能都在这条线上实现通讯。app
watch:{
selected(){
if(this.selected&&........{
this.$refs.item.style.backgroundColor = ........
this.$refs.item.style.color = .........
//...........
}else{
//............
}
}
}
复制代码
element对弹出框的操做是以在body
上添加子节点的方式。这样子最直接的就是避免了可能存在overflow:hideen
的问题。 后面的定位只须要用js来完成就好了,还要考虑到scroll
的问题。
const {trigger} = this.$refs
document.body.appendChild(trigger)
复制代码
获取 let {top,left,height,width} = '目标元素的位置'.getBoundingClientRect()
考虑到window的scrollY
和scrollX
对弹出框定位计算的影响
let position = {
'某个位置':{
top:top + window.scrollY,
left:left + window.scrollX,
//.......
},
}
复制代码
定位
trigger.style.left = position['某个位置'].left + 'px'
trigger.style.top = position['某个位置'].top + 'px'
复制代码
代码粗略了一点,差很少是这个意思,虽然还有其余须要考虑的地方。大致就是这样了吧。
最麻烦的地方在于动画过渡
上,单纯的用css会有弹出框回到原点的问题。 这里就须要用到 javascript钩子。 官网在这上面的说的很明确,可是实现过程当中也踩了一些坑
JavaScript
作过渡的时候(没用css)的状况下动画会瞬间完成,要使用done。以后试了并无用。这里并非只用JavaScript作过渡的。以后想了一下,既然beforeEnter
和enter
瞬间就执行了,并无动画的事件间隔,为何不本身加一个 settimeout
呢,问题就解决了,可能这种解决方法并非很好。这里不用$nextTick
的缘由enter(el) {
setTimeout(()=>{
//.....
})
},
复制代码
afterEnter
改回来啊,其中就例如overflow:hidden
和height
,致使在vertical
垂直面板上弹出框不会撑开父元素的问题。afterLeave(el){
el.style.overflow = ''
if(this.vertical){
el.style.height=''
}
},
复制代码
后面就是和定位弹出框同样相似的js操做,在menu
导航菜单里面我并无这么作,后面会改为这样的吧。
beforeEnter(el) {
el.style.position= 'absolute'
el.style.transform=.......
//.........
},
enter(el) {
//......
},
复制代码
hover
触发和click
触发menu-item-group
组件的添加,其实只是做为一个中转站而已,无非是拷贝一些函数。active
下面的亮条显示,一开始就是设置menu-item
里面的border-bottom
,由于同时设置了transition
。在显示的时候会有高度抖动的问题。后来改用伪类完成,不过在自定义颜色的时候很是麻烦。这个方法也放弃了。最后只有在下面加一个div
代替border-bottom
做为高亮线条,方法虽然很蠢,可是有效。scoped
里面给某些子组件添加css样式用到了 /deep/
深度做用。其实以这种显而易见的数据流做为基础,多增长一些功能无非是函数的添加和css的修改。找bug和维护也是相对比较轻松的。最后您若是以为还不错的话,给个人项目一个star想必也是极好的。