前言
最近搞了些前端的工作,本来做菜单栏的时候想直接用 bootstrap 的,但是感觉 bootstrap 太大了,而且依赖有点多,在 webpack 中也不是很好打包(虽然可以绕过去),所以就索性自己在网上找了一些实现方式,改改感觉也还可以。这次主要实现了两种菜单栏,具体如下。
鼠标悬停下拉菜单
鼠标悬停下拉菜单应该是最常见的一种菜单栏了,当鼠标悬停在菜单栏上时,子菜单缓缓下拉,看起来就很舒服,用 flex 布局结合列表也很好实现(不用像以前那种 float
了,舒服)。具体实现方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <style> .flex-body { display: flex; flex-direction: column; }
header { z-index: 1; }
ul, ul li { list-style: none; margin: 0; padding: 0; }
.menu { display: flex; justify-content: start; }
ul li { width: 100px; height: 50px; line-height: 50px; }
.menu li .submenu { background-color: aqua; }
.submenu li { height: 0; line-height: 0; opacity: 0; visibility: hidden; }
.menu li:hover .submenu li { height: 50px; line-height: 50px; opacity: 1; visibility: visible; transition: all 1s; } </style> <div class="flex-body"> <header> <ul class="menu"> <li> <a>menu1</a> <ul class="submenu"> <li><a>submenu1</a></li> <li><a>submenu1</a></li> <li><a>submenu1</a></li> </ul> </li> <li> <a>menu2</a> <ul class="submenu"> <li><a>submenu1</a></li> <li><a>submenu1</a></li> <li><a>submenu1</a></li> </ul> </li> <li> <a>menu3</a> <ul class="submenu"> <li><a>submenu1</a></li> <li><a>submenu1</a></li> <li><a>submenu1</a></li> </ul> </li> </ul> </header> <main>mainmainmainmainmainmainmainmainmainmain</main> </div>
|
※注: 实现该菜单栏有两个需要注意的点:1、不能用 display: none
和 block
来使子菜单消失或出现,因为这样会造成缓缓下拉的动画失效,transition
并不支持 display
,所以只能用 visibility
和 height
来共同实现,以达到下拉动画效果;2、因为使用了 flex
布局,所以 z-index
只对同级 flex-item
有效,所以为防止菜单栏下面的内容出现在子菜单之上,即将子菜单栏位于最上层,需要将整个页面的布局都设置为 flex
,并使 header
的 z-index
最大,如此才能保证子菜单的菜单覆盖 main
中的内容,不然就会有重叠干扰现象。
鼠标点击手风琴菜单
手风琴特效也算是非常常见的了,一般的手风琴是鼠标悬停展开,这种比较好实现,难的是如何保持这种展开状态,focus
可以短暂保持展开状态,但是不能点击其他地方,局限性太大。所以需要引入其它的东西来记录这种展开状态,可以用 checkbox
或 radio
的 checked
来记录这种状态,从而只用 css 即可实现该菜单,具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <style> input[data-prop="menu-recorder"] { display: none; }
.menu-title { display: block; width: 500px; height: 50px; line-height: 50px; border: 1px solid black; }
.menu-content { width: 500px; max-height: 100px; overflow-y: auto; }
.menu-content>li { height: 0; line-height: 20px; overflow: auto; opacity: 0; visibility: hidden; transition: all 1s; }
input[data-prop="menu-recorder"]:checked+.menu-content>li { height: 20px; line-height: 20px; opacity: 1; visibility: visible; transition: all 1s; } </style> <div class="accordion-menu"> <section class="menu-item"> <label class="menu-title" for="menu1">menu1</label> <input id="menu1" type="checkbox" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu2">menu2</label> <input id="menu2" type="checkbox" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu3">menu3</label> <input id="menu3" type="checkbox" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu4">menu4</label> <input id="menu4" type="checkbox" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> </div>
|
这里需要注意一点的就是 height 从 0 到 100% 并不会触发 transition 渐变动画,而是需要确切的高度值变化才能触发,所以上文这里添加了个 <li>
标签,直接在该标签上添加动画,还有一种就是在 .menu-content
上设定确定的 max-height
,也能触发动画,但是有个缺点就是前后 max-height
的差距太大时,动画效果就很不理想了,这时可能只能依靠 js 了。上文中 checkbox
也可用 radio
替换,效果略有差异,一个是能全部展开或收起,而另一个则是能且仅能展开一个。
Tab 标签页切换菜单
这个菜单和上面那个菜单的实现非常相似,先上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <style> .tab-menu { display: flex; position: relative; width: 500px; height: 300px; }
input[data-prop="menu-recorder"] { display: none; }
.menu-title { display: block; width: 100px; line-height: 50px; text-align: center; border: 1px solid black; border-right: 0; box-sizing: border-box; transition: all 1s; }
.tab-menu .menu-item:last-child .menu-title { border-right: 1px solid black; }
.menu-content { position: absolute; left: 0; top: 51px; height: calc(100% - 50px); overflow-y: auto; width: 100%; border: 1px solid #000; box-sizing: border-box; font-size: 24px; text-align: center; opacity: 0; visibility: hidden; transition: all 1s;
}
input[data-prop="menu-recorder"]:checked+.menu-content { opacity: 1; visibility: visible; transition: all 1s; } </style> <div class="tab-menu"> <section class="menu-item"> <label class="menu-title" for="menu1">menu1</label> <input id="menu1" type="radio" name="tab-control" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu2">menu2</label> <input id="menu2" type="radio" name="tab-control" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu3">menu3</label> <input id="menu3" type="radio" name="tab-control" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> <section class="menu-item"> <label class="menu-title" for="menu4">menu4</label> <input id="menu4" type="radio" name="tab-control" data-prop="menu-recorder"> <div class="menu-content"> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> <li>test<br></li> </div> </section> </div>
|
从代码上看关键点就是 .menu-content
的定位方式了,采用了绝对定位,并将整个菜单栏设置为相对定位,以保证所有 tab 标签页内容位置和大小保持一致。当然 tab 标签页肯定是唯一的,所以只能用 radio
记录显示标签页了。其中为了保证 .menu-title
和 .menu-content
的边框不重叠,所以在 .menu-title
中只设置 line-height
,而 .menu-content
的 top
比其多一个像素。
后记
这三种菜单应该是最常见也是用的最多的了,纯 css 实现的方式也比较类似,无非就是 flex 布局以及借助 css3 强大的选择器功能(父类选择器不知要到猴年马月了,比较遗憾 😥),就能相对简单的实现了,当然借助一些 js 库或框架可能会更简单一些 ,但能用 css 为何不用呢 😄。
参考资料
[1] 利用flex实现的二级导航栏
[2] CSS3动画下拉菜单(当transition遇到display的坑)
[3] CSS3手风琴下拉菜单
[4] 教你两招用纯CSS写Tab切换