前言

实习期的时候接手一个网站主页菜单风格调整的需求,当时虽然做出来了,但是过程坎坷,写这篇文章是为了复盘一下。

站在不同的视角,也许会有不同的收获,当时觉得难得点,现在看来,也还好。

需求出来的时候,运营组同事来找开发要人。

当时我还空闲着,导师问:新风格级联菜单调整需求想做吗?

我反问:我能做吗?

导师很肯定的说:能,当然能。

需求分析

因为是内部业务,具体菜单名就不公开了,需求效果图简化版如下:

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力效果图

需求点拆解

  1. 三级菜单联动,通过鼠标悬浮事件触发
  2. 二三级子菜单跟随一级菜单移动(包括顶部小三角)
  3. 二级菜单项>10&&<=20,高度*2
  4. 二级菜单项>20,高度不变,出滚动条
  5. 三级菜单项>10但<=20,宽度*2
  6. 三级菜单项>20但<=30,宽度*3
  7. 三级菜单项>30,出滚动条
  8. 菜单联动时,滑动不可太快
  9. 鼠标离开展开菜单所在范围后,菜单不收起,点击旁白处收起

拿到需求后做什么?

很多人拿到需求后想了想就开始敲代码,然后发现想的有瑕疵,返工,返工,无意义返工...

最初我也是这样,结果搞了一周的时间,乱八七糟,心态崩了。

吾日三省吾身:好菜好菜好菜!

接手这个需求的时候,是在原有项目上改造,几乎没有注释,读起来好难搞...

设计先行

正所谓磨刀不误砍柴工,好的设计,能在很大程度上帮你避免返工,

在我开始怀疑人生的时候,导师耐心的手把手教学,教我如何考虑菜单设计。

一步步的有了柳暗花明的感觉,哇,刺激!

如何提问?

提问真的是一门艺术,切记笼统宽泛。

你哪里不懂?都不懂。如果是这样,那就没必要再交流了。

导师问我卡在哪,我具体的描述了哪个功能点不清楚,然后具体的给我讲。

除此之外,我将自己已有的方案和导师继续沟通,看是否可行,是否存在局限。

大概搞了一下午,然后重新开工,删掉之前的垃圾代码。

此时的心态逐步稳了,感谢导师,人超nice。

ok ,下边开始设计走起。不同的角度,不同的风景。

菜单设计

菜单树设计

一个级联菜单的展现需要怎样的数据结构?仅这个需求而言,结构还是比较简单的。

注意接下来的分析思路:

  1. 每个菜单项需要有一个具有某些特征的id,用于标识唯一性和级联关系
  2. 每个菜单项都有内容展示,这里就叫label.
  3. 带有链接的菜单项是可以点击跳转的,所以需要一个跳转地址,这里就叫href.
  4. 既然是级联菜单,树形结构,那就需要一个字段标识其子节点,这里就叫nodes
  5. 综上,这个简单的菜单树结构就出来了
  •  
[	{		"id": "1",		"nodes": [{			"id": "1111",			"nodes": [					{					"id": "111101",					"nodes": [],					"label": "三级菜单",					"href": "/"				}			],			"label": "二级菜单",			"href": "/"			}],			"label": "一级菜单",		"href": "/"	}]

一级菜单

从效果图和需求角度考虑,一定是先有一级菜单才考虑后续的联动展现。

所以,第一步是让一级菜单渲染,这一步其实很简单, 就是拿到数据遍历一下。

除此之外,想想还有什么?高亮控制二级菜单渲染

此时二级菜单可以看做一个黑盒,你不必过早分析二级菜单内部是什么,就先给个占位就好。

综上,我们知道对一级菜单渲染只需要接收一个带有list的props即可,然后自身有一个state用于高亮控制

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力_02一级菜单分析

ok,,到这里,我们就完成了一级菜单的渲染和高亮控制。

至于二三级菜单的跟随移动,其实让一级菜单相对定位,二三级菜单绝对定位就好。

二级菜单

接下来我们先想一下二级菜单需要接收什么属性自身需要什么状态是否需要某些方法

最直观的一点,二级菜单也是需要渲染的,所以需要列表数据,也就是一级菜单的每一个数据项的nodes属性

此外,二级菜单还需要有展开状态,控制菜单的收起和展开,而且这个状态应该由一级菜单控制

为什么?因为鼠标滑动事件逻辑触发,最初作用的就是一级菜单。

那什么情况下,确定是展开呢?

还记得一级菜单遍历时候每一项有一个id嘛,这个id和鼠标滑过时更新的selectId相等,并且nodes不为空,意味着展开

所以,二级菜单需要接收的属性有两个,list和open

也许你会好奇,open是怎么作用上去的,大道至简,条件渲染配合display:none or block即可

ok,我们补充一下上边占位的二级菜单需要接收的属性。

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力_03二级菜单需要接收的属性

分析完了需要接收的属性,我们再想想二级菜单有什么特别的?

还真有,除了高亮还需要动态计算高度,所以需要一个computed方法和height状态

别忘了三级菜单的占位,套路相同,传递的属性也一样的

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力_04二级菜单高度计算

细心的你也许发现了这部分对鼠标滑入和滑出动作做了一个延迟处理

主要针对的是二级菜单侧滑到三级菜单会触发两次更新导致闪动的问题。

三级菜单

有了以上的分析基础,三级菜单部分没什么特别的,只是在计算时候多了一个宽度计算。

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力_05三级菜单

到这里,上述提到的需求点还差第9条未实现,这部分可以使用蒙版来完成

蒙版的使用

蒙版不是一个什么神奇的存在,其实就是一层遮罩。

第九条需求是鼠标滑出展开的菜单区域,不收起,点击旁白处收起。

我们来思考一下,菜单收起的逻辑是怎样的?

是不是一级菜单收起就可以了?是的,没毛病。

触发一级菜单收起的条件,就是非选中状态,将selectId置为默认空字符串即可

知道了处理逻辑,还有最后一点,旁白区怎么锁定?

用定位

在浏览器中fixed定位和absolute定位在不设置层级情况下,默认后写的会覆盖先写的。

我们可以让一级菜单展开的时候同时打开一个不可见的全屏蒙版,然后在点击蒙版的时候将selectId置为默认空字符串

因为二三级菜单都是有定位的,所以会覆盖蒙版,压在其上方,即使点击也不会导致菜单收起

蒙版展开看起来像下图这样:注意把顶部菜单位置预留出来,加个top就好

为了方便演示,我加了红色背景,实际开发时去掉背景色就好

设计的魅力(二): 导师问:新风格级联菜单调整需求想做吗?我:我能做吗?_设计的魅力_06蒙版示意图

总结

级联菜单的实现,主要考虑的两个点是菜单树的设计以及联动如何完成。

搞清楚这两个点,其他的都好解决。

当思绪变的连贯,设计明确,分析每个菜单需要什么,自己应该有什么,做起来就是水到渠成。

工作时常常提醒自己:设计先行,设计先行,设计先行!

源码和模拟数据都放在这里了,需要的自取,仅供参考

https://github.com/lengyuexin/menu


情如风雪无常,

却是一动既殇。

感谢你这么好看还来阅读我的文章,

我是冷月心,下期再见。