改进菜单栏动态展示样式,我被评上优秀开发!
点击关注公众号,“技术干货”及时达!背景我们公司的导航菜单是动态可配置的,有的页面菜单数量比较多,有的比较少。
由于大多页面菜单都是比较少的,因此当菜单非常多时, 我们采用了朴实无华的滚动条:当横向超出的时候,滚动展示。
但很快,客户就打回来了:说我们的样式太丑,居然用滚动条!还质问我们产品这合理吗?产品斩钉截铁的告诉客户,我让开发去优化...
于是,领导让我们想解决方案。(我真谢谢产品!)
很快,我想到一个方案(从其他地方看到的交互),我告诉领导:
我们可以做成动态菜单栏,如果展示不下了,出现一个更多按钮,多余的菜单都放到更多里面去:
领导说这个想法不错啊,那就你来实现吧!
好家伙,我只是随便说说,没想到,自己给自己挖了个大坑啊!
不过,我最后也是顺利的完成了这个效果的开发,还被评上了本季度优秀开发!分享一下自己的实现方案吧!
技术方案基础组件样式开发既然要开发这个效果,干脆就封装一个通用组件AdaptiveMenuBar.vue吧。我们先写一下基本样式,如图,灰色区域就是我们的组件内容,也就是我们菜单栏动态展示的区域。
AdaptiveMenuBar.vue
template
divclass="adaptive-menu-bar"
/div
/template
stylelang="less"scoped
.adaptive-menu-bar{
width:100%;
height:48px;
background:gainsboro;
display:flex;
position:relative;
overflow:hidden;
}
/style
我们写点假数据
template
divclass="adaptive-menu-bar"
divclass="origin-menu-item-wrap"
divv-for="(item,index)inmenuOriginData":key="index"class="menu-item"
{{item.name}}
/div
/div
div更多/div
/div
/template
scriptsetup
constmenuOriginData=[
{name:'哆啦a梦',id:1},
{name:'宇智波佐助',id:1},
{name:'香蕉之王奥德彪',id:1},
{name:'漩涡鸣人',id:1},
{name:'雏田',id:1},
{name:'大雄',id:1},
{name:'源静香',id:1},
{name:'骨川小夫',id:1},
{name:'超级马里奥',id:1},
{name:'自来也',id:1},
{name:'孙悟空',id:1},
{name:'卡卡罗特',id:1},
{name:'万年老二贝吉塔',id:1},
{name:'小泽玛丽',id:1}
];
/script
stylelang="less"scoped
.adaptive-menu-bar{
width:100%;
height:48px;
background:gainsboro;
display:flex;
position:relative;
overflow:hidden;
.origin-menu-item-wrap{
width:100%;
display:flex;
}
}
/style
如图,由于菜单数量比较多,一部分已经隐藏在origin-menu-item-wrap这个父元素里面了。
实现思路那我们要如何才能让多余的菜单出现在【更多】按钮里呢?原理很简单,我们只要计算出哪个菜单超出展示区域即可。假设如图所示,第12个菜单被截断了,那我们前11个菜单就可以展示在显示区域,剩余的菜单就展示在【更多】按钮里。
更多按钮的展示逻辑更多按钮只有在展示区域空间不够的时候出现,也就是origin-menu-item-wrap元素的滚动区域宽度scrollWidth 大于其宽度clientWidth的时候。
用代码展示大致如下
template
divref="menuBarRef"class="origin-menu-item-wrap"
divv-for="(item,index)inmenuOriginData":key="index"class="menu-item"
m-buttontype="default"size="small"{{item.name}}/m-button
/div
/div
/template
scriptsetup
constmenuOriginData=[
{name:'哆啦a梦',id:1},
{name:'宇智波佐助',id:1},
{name:'香蕉之王奥德彪',id:1},
{name:'漩涡鸣人',id:1},
{name:'雏田',id:1},
{name:'大雄',id:1},
{name:'源静香',id:1},
{name:'骨川小夫',id:1},
{name:'超级马里奥',id:1},
{name:'自来也',id:1},
{name:'孙悟空',id:1},
{name:'卡卡罗特',id:1},
{name:'万年老二贝吉塔',id:1},
{name:'小泽玛丽',id:1}
];
//是否展示更多按钮
constshowMoreBtn=ref(false);
onMounted(()={
constmenuWrapDom=menuBarRef.value;
if(menuWrapDom.scrollWidthmenuWrapDom.clientWidth){
showMoreBtn.value=true;
}
});
/script
截断位置的计算要计算截断位置,我们需要先渲染好菜单。
然后开始对menu-item元素宽度进行加和,当相加的宽度大于菜单展示区域的宽度clientWidth时,计算终止,此时的menu-item元素就是我们要截断的位置。
菜单截断的部分,我们此时放到更多里面展示就可以了。
大致代码如下:
template
divref="menuBarRef"class="origin-menu-item-wrap"
divv-for="(item,index)inmenuOriginData":key="index"class="menu-item"
m-buttontype="default"size="small"{{item.name}}/m-button
/div
/div
/template
scriptsetup
constmenuOriginData=[
{name:'哆啦a梦',id:1},
{name:'宇智波佐助',id:1},
{name:'香蕉之王奥德彪',id:1},
{name:'漩涡鸣人',id:1},
{name:'雏田',id:1},
{name:'大雄',id:1},
{name:'源静香',id:1},
{name:'骨川小夫',id:1},
{name:'超级马里奥',id:1},
{name:'自来也',id:1},
{name:'孙悟空',id:1},
{name:'卡卡罗特',id:1},
{name:'万年老二贝吉塔',id:1},
{name:'小泽玛丽',id:1}
];
//是否展示更多按钮
constshowMoreBtn=ref(false);
onMounted(()={
constmenuWrapDom=menuBarRef.value;
if(menuWrapDom.scrollWidthmenuWrapDom.clientWidth){
showMoreBtn.value=true;
}
//计算截断菜单的索引位置
letsliceIndex=0
//获取menu-item元素dom的集合
constmenuItemNodeList=menuWrapDom.querySelectorAll('.menu-item');
//将NodeList转换成数组
constnodeArray=Array.prototype.slice.call(menuItemNodeList);
letaddWidth=
for(leti=inodeArray.length;i++){
constnode=nodeArray[i];
//clientWidth不包含菜单的margin边距,因此我们手动补上12px
addWidth+=node.clientWidth+
//76是更多按钮的宽度,我们也要计算进去
if(addWidth+76middleDom.clientWidth){
sliceIndex.value=
break;
}else{
sliceIndex.value=
}
}
});
/script
样式重整当被截断的元素计算完毕时,我们需要重新进行样式渲染,但是注意,我们原先渲染的菜单列不能注销,因为每次浏览器尺寸变化时,我们都是基于原先渲染的菜单列进行计算的。
所以,我们实际需要渲染两个菜单列:一个原始的,一个样式重新排布后的。
如上图,黄色就是原始的菜单栏,用于计算重新排布的菜单栏,只不过,我们永远不在页面上展示给用户看!
template
divclass="adaptive-menu-bar"
!--原始渲染的菜单栏--
divref="menuBarRef"class="origin-menu-item-wrap"
divv-for="(item,index)inmenuOriginData":key="index"class="menu-item"
m-buttontype="default"size="small"{{item.name}}/m-button
/div
/div
!--计算优化显示的菜单栏--
divv-for="(item,index)inmenuList":key="index"class="menu-item"
m-buttontype="default"size="small"{{item.name}}/m-button
/div
div更多/div
/div
/template
代码实现基础功能完善为了我们的菜单栏能动态的响应变化,我们需要再每次resize事件触发时,都重新计算样式
constmenuOriginData=[
{name:'哆啦a梦',id:1},
{name:'宇智波佐助',id:1},
{name:'香蕉之王奥德彪',id:1},
{name:'漩涡鸣人',id:1},
{name:'雏田',id:1},
{name:'大雄',id:1},
{name:'源静香',id:1},
{name:'骨川小夫',id:1},
{name:'超级马里奥',id:1},
{name:'自来也',id:1},
{name:'孙悟空',id:1},
{name:'卡卡罗特',id:1},
{name:'万年老二贝吉塔',id:1},
{name:'小泽玛丽',id:1}
];
//是否展示更多按钮
constshowMoreBtn=ref(false);
constsetHeaderStyle=()={
//....
}
window.addEventListener('resize',()=setHeaderStyle());
onMounted(()={
setHeaderStyle();
});
/script
完整代码完整代码剥离了一些第三方UI组件,便于大家理解。
template
divclass="adaptive-menu-bar"
!--原始渲染的菜单栏--
divref="menuBarRef"class="origin-menu-item-wrap"
divv-for="(item,index)inmenuOriginData":key="index"class="menu-item"
{{item.name}}
/div
/div
!--计算优化显示的菜单栏--
divv-for="(item,index)inmenuList":key="index"class="menu-item"
{{item.name}}
/div
!--更多按钮--
divv-if="showMoreBtn"class="dropdown-wrap"
span更多/span
!--更多里面的菜单--
divclass="menu-item-wrap"
divv-for="(item,index)inmenuOriginData.slice(menuList.length)":key="index"{{item.name}}/div
/div
/div
/div
/template
scriptsetup
import{IconMeriComponentArrowDown}from'meri-icon';
constmenuBarRef=ref();
constopen=ref(false);
constmenuOriginData=[
{name:'哆啦a梦',id:1},
{name:'宇智波佐助',id:1},
{name:'香蕉之王奥德彪',id:1},
{name:'漩涡鸣人',id:1},
{name:'雏田',id:1},
{name:'大雄',id:1},
{name:'源静香',id:1},
{name:'骨川小夫',id:1},
{name:'超级马里奥',id:1},
{name:'自来也',id:1},
{name:'孙悟空',id:1},
{name:'卡卡罗特',id:1},
{name:'万年老二贝吉塔',id:1},
{name:'小泽玛丽',id:1}
];
constmenuList=ref(menuOriginData);
//是否展示更多按钮
constshowMoreBtn=ref(false);
constsetHeaderStyle=()={
constmenuWrapDom=menuBarRef.value;
if(!menuWrapDom)return;
if(menuWrapDom.scrollWidthmenuWrapDom.clientWidth){
showMoreBtn.value=true;
}else{
showMoreBtn.value=false;
}
constmenuItemNodeList=menuWrapDom.querySelectorAll('.menu-item');
if(menuItemNodeList){
letaddWidth=0,
sliceIndex=0;
//将NodeList转换成数组
constnodeArray=Array.prototype.slice.call(menuItemNodeList);
for(leti=0;inodeArray.length;i++){
constnode=nodeArray[i];
addWidth+=node.clientWidth+12;
if(addWidth+64+12menuWrapDom.clientWidth){
sliceIndex=
break;
}else{
sliceIndex=0;
}
}
if(sliceIndex0){
menuList.value=menuOriginData.slice(0,sliceIndex);
}else{
menuList.value=menuOriginData;
}
}
};
window.addEventListener('resize',()=setHeaderStyle());
onMounted(()={
setHeaderStyle();
});
/script
stylelang="less"scoped
.adaptive-menu-bar{
width:100%;
height:48px;
background:gainsboro;
display:flex;
position:relative;
align-items:center;
overflow:hidden;
.origin-menu-item-wrap{
width:100%;
display:flex;
position:absolute;
top:49px;
display:flex;
align-items:center;
left:0;
right:0;
bottom:0;
height:48px;
z-index:9;
}
.menu-item{
margin-left:12px;
}
.dropdown-wrap{
width:64px;
display:flex;
align-items:center;
cursor:pointer;
justify-content:center;
height:28px;
background:#fff;
border-radius:4px;
overflow:hidden;
border:1pxsolid#c4c9cf;
background:#fff;
margin-left:12px;
.icon{
width:16px;
height:16px;
margin-left:4px;
}
}
}
/style
代码效果可以看到,非常丝滑!
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线