仿Bootstrap自适应导航栏

一个由CSS+少量JS实现的自适应导航栏,小屏幕自动折叠,带动画效果。

一、缘起

PC端几乎每个页面都需要导航栏,其作用在于提供快速“导航”功能,Bootstrap的自适应导航栏能自适应折叠,美观实用,但是缺点是不够酷炫,如果你想在其中加入某些样式可能会与原始样式发生冲突,造成某些莫名其妙的冲突,而且你要使用Bootstrap的自适应导航栏必须要引入Bootstrap的CSS文件和JS文件
以下的实现并没有参考Bootstrap的实现方式,代码也不够简洁,可能存在冗余,先看效果吧:

二、HTML结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<nav class="zj_nav">
<ul>
<li class="menu">
<div class="menu_bar"></div>
<div class="menu_bar"></div>
<div class="menu_bar"></div>
</li>
<li class="nav_header">Collapsible Nav</li>
<li class="cur_page"><a href="##">首页</a></li>
<li><a href="##">产品与解决方案</a></li>
<li><a href="##">服务与支持</a></li>
<li><a href="##">工作机会</a></li>
<li><a href="##">关于</a></li>
</ul>
</nav>
<div class="content">正文内容。</div>

<footer>
<a href="http://woshizja.github.io/">blog</a>
</footer>

其中:

  • menu类的li用于设置“汉堡菜单”;
  • 中间的三个div就是菜单的三条“横线”;
  • nav_header类用于显示导航栏的头部信息,这部分不会折叠,始终显示;
  • cur_page类用于标识当前页,样式会有所区别;
  • 剩下的<li>标签代表一个一个的导航项;

三、CSS结构

CSS一共有120行左右,当然因为我这里加入了一些动画效果所以显得多一点,下面说几个关键的地方:

  • 导航栏的每一项都设置为display:inline-block

    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
    .zj_nav {
    width: 100%;
    display: inline-block;
    overflow: hidden;
    font-size: 1.1em;
    height: 3em; /*这里设置的高一些是为了显示底部的小三角*/
    margin-bottom: 2em;
    }

    .zj_nav>ul {
    display: inline-block;
    width: 100%;
    background: #ddd;
    min-height: 2.5em;
    margin: 0;
    padding: 0; /*这里一定要设置padding来消除li的缩进*/
    }

    .zj_nav li {
    list-style: none;
    height: 2.5em;
    line-height: 2.5em;
    display: inline-block;
    background: #ddd;
    float: left;
    }
  • 使用not为一般的导航项设置样式:

    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
    .zj_nav li:not(.menu):not(.nav_header) {  /*not排除含有menu和nav_header类名的导航项*/
    padding: 0 1em;
    position: relative;
    transition: background 300ms linear;
    }

    .zj_nav li:not(.menu):not(.nav_header):after { /*用after伪元素做动画*/
    content: "";
    width: 0%;
    height: 3px;
    background: #48771B;
    position: absolute;
    right: 0;
    bottom: 0;
    transition: width 300ms linear;
    }

    .zj_nav li:not(.menu):not(.nav_header):hover:after {/*同时使用hover和after/before时需要把hover写在前面*/
    width: 100%;
    left: 0; /*这里设置left和前面的right相对应,实现底部动画的流畅过渡*/
    }

    .zj_nav>ul>li:not(.nav_header):hover {
    background: #ccc;
    }
  • 使用before伪元素为当前页设置小三角标识:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .zj_nav ul li.cur_page:before {
    content: "";
    position: absolute;
    z-index: 100;
    top: 2.5em;
    margin-left: -8px;
    width: 0;
    height: 0;
    border-left: 8px solid transparent; /*通过设置边框画出三角形*/
    border-right: 8px solid transparent; /*transparent标识背景透明*/
    border-top: 8px solid #ccc; /*这里的原理相当于使用左右边框占据部分底部边框的空间,使得顶部显示为三角形*/
    }
  • 使用@media设置自适应折叠导航栏:

    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
    .zj_nav>ul>li.collapse:not(.menu) {
    display: inline-block;
    }
    @media screen and (max-width:768px) {
    .zj_nav {
    height: auto; /*原始高度是固定的,这里设置auto才能撑开内容*/
    }
    li.menu {
    display: inline-block; /*显示汉堡菜单*/
    }
    li.collapse:not(.menu):not(.nav_header) {
    display: none; /* 设置JS加入的collapse样式为隐藏,用于切换显示*/
    }
    .zj_nav li:not(.menu):not(.nav_header) {
    display: none; /* 隐藏导航项*/
    width: 100%;
    border-left: 3px solid #748690;
    }
    .zj_nav li:not(.menu):not(.nav_header):hover:after {
    width: 100%;
    height: 2px;
    }
    .zj_nav ul li.cur_page:before {
    display:none;
    }
    }
  • 这里有个关键技巧:使用CSS选择器的优先级来巧妙地实现导航项的显示与隐藏:

    • 点击菜单会给导航项添加/删除CSS类:collapse
    • 屏幕小于768px时含有collapse类的样式刚好相反,一个隐藏一个显示,优先级是media外面的大于media里面的
    • 当第一次点击菜单时,导航项被添加collapse类,这时由于外面的样式优先级高,所以导航项可以显示出来;
    • 当第二次点击菜单时,collapse类被移除,这时因为media内部关于导航项的样式和外部的优先级一样,但是出现的位置靠后,所以执行内部的样式,实现隐藏;
    • 这时如果屏幕宽度变大,大于768px时,media内部样式失效,执行外部的样式,导航项重新显示。

四、JS

因为要实现点击菜单切换显示导航栏,所以少量的JS是必要的,这里的实现使用了classListtoggle方法,没有考虑兼容性,一共只有12行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
document.getElementsByClassName("menu")[0].addEventListener("click", function(e) {
var eles;
//判断是不是LI是为了:点击li或者li中的div都能切换显示导航栏
if (e.target.tagName === "LI") {
eles = e.target.parentNode.children;
} else {
eles = e.target.parentNode.parentNode.children;
}
var len = eles.length;
for (var i = 2; i < eles.length; i++) {
eles[i].classList.toggle("collapse");
}
});