响应式多级菜单 侧边菜单栏
如果您曾经在响应式网站上工作,那么毫无疑问,您必须解决这个新兴领域中最棘手的问题之一:导航。 对于简单的导航,解决方案可以很简单。 但是,如果您要处理的事情比较复杂,可能有多个嵌套列表和下拉列表,则可能需要进行更戏剧性的重排。
在这种响应式导航方法中,我们将使用一种可以使用媒体查询和jQuery容纳大型多级导航菜单的方法,同时尝试使我们的标记简单而我们的外部资源最少。
寻找快速解决方案?
如果您正在寻找一种快速的解决方案,那么Envato Market上有许多CSS导航菜单和样式集。
这个CSS3 Mega下拉菜单特别好用-它仅依赖CSS / XHTML,并具有完全有效的联系表以及对启动具有完全响应的能力。 包括三个主要变体:水平,垂直对齐在左侧和垂直对齐在右侧。
您也可以与Envato Studio上的专家提供商之一合作 。
目标:响应式下拉菜单
这是我们的目标:
- 在较大的屏幕上,显示一个水平下拉菜单,当父元素悬停在上方时,最多显示2级子菜单。
- 在较小的屏幕上,有一个“菜单”按钮,可垂直显示我们的菜单,在单击/触摸父元素时显示子菜单。
步骤1:标记
我们的标记是一个相当简单的无序列表,嵌套列表包含在列表项中。 我故意在父元素无序列表上不使用任何类或ID,以便该菜单与CMS生成的菜单兼容。
<div class="container">
<a class="toggleMenu" href="#">Menu</a>
<ul class="nav">
<li class="test">
<a href="#">Shoes</a>
<ul>
<li>
<a href="#">Womens</a>
<ul>
<li><a href="#">Sandals</a></li>
<li><a href="#">Sneakers</a></li>
<li><a href="#">Wedges</a></li>
<li><a href="#">Heels</a></li>
<li><a href="#">Loafers</a></li>
<li><a href="#">Flats</a></li>
</ul>
</li>
<li>
<a href="#">Mens</a>
<ul>
<li><a href="#">Loafers</a></li>
<li><a href="#">Sneakers</a></li>
<li><a href="#">Formal</a></li>
</ul>
</li>
</ul>
</li>
步骤2:基本样式
让我们添加一些非常基本的样式,使我们的列表看起来像一个导航栏:
body, nav, ul, li, a {margin: 0; padding: 0;}
body {font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
a {text-decoration: none;}
.container {
width: 90%;
max-width: 900px;
margin: 10px auto;
}
.toggleMenu {
display: none;
background: #666;
padding: 10px 15px;
color: #fff;
}
.nav {
list-style: none;
*zoom: 1;
background:#175e4c;
position: relative;
}
.nav:before,
.nav:after {
content: " ";
display: table;
}
.nav:after {
clear: both;
}
.nav ul {
list-style: none;
width: 9em;
}
.nav a {
padding: 10px 15px;
color:#fff;
*zoom: 1;
}
.nav > li {
float: left;
border-top: 1px solid #104336;
z-index: 200;
}
.nav > li > a {
display: block;
}
.nav li ul {
position: absolute;
left: -9999px;
z-index: 100;
}
.nav li li a {
display: block;
background: #1d7a62;
position: relative;
z-index:100;
border-top: 1px solid #175e4c;
}
.nav li li li a {
background:#249578;
z-index:200;
border-top: 1px solid #1d7a62;
}
我们刚刚将列表项浮动到一条水平线上,设置了一些颜色,并使用绝对定位将子菜单隐藏在屏幕之外。 如果您对第20行感到疑惑,那么这是一种简单的clearfix方法,我发现它在这种情况下有效(请参阅Nicolas Gallagher的博客上的更多信息 )。
第3步:水平下拉菜单
接下来,让我们进行水平下拉菜单。 尽管可以使用纯CSS使用:hover
伪选择器来完成此操作,但是我将使用JavaScript将其添加进来,因为我们将要切换菜单以在小屏幕版本中单击激活。
由于我们使用绝对定位将子菜单移出屏幕,因此我们添加一些.hover
规则,当存在.hover
类时,这些规则将相对于其父项定位子菜单(我们将使用jQuery来解决)。
.nav li {
position: relative;
}
.nav > li.hover > ul {
left: 0;
}
.nav li li.hover ul {
left: 100%;
top: 0;
}
现在,我们将添加几行简单的jQuery,以将.hover类添加到将元素悬停在列表元素上时。
$(document).ready(function() {
$(".toggleMenu").css("display", "none");
$(".nav li").hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
});
这样,我们就有了一个功能齐全的多层下拉菜单。
步骤4:垂直下拉式选单
不幸的是,我们可爱的水平下拉菜单在移动屏幕上看起来很小,所以让我们确保通过添加viewport meta标签来在移动浏览器加载页面时将其完全放大。
<meta name="viewport" content="width=device-width, initial-scale=1">
当然,现在我们的下拉菜单在移动设备上看起来更糟,并且大多数甚至都无法显示在屏幕上! 让我们添加一些媒体查询,以将列表样式设置为断点下方的垂直列表。 我们的断点由菜单分成两行的点确定,在我的情况下,大约为800px。
在断点处,我们将删除浮点数,并将列表项和无序列表设置为width: 100%
。 现在,当我们将鼠标悬停在父项上时,其子项列表将显示在其下项的顶部。 我们宁愿其他顶级列表项被取代。 因此,我们将position
值设置为static
,而不是更改无序列表的left
位置。
@media screen and (max-width: 800px) {
.nav > li {
float: none;
}
.nav ul {
display: block;
width: 100%;
}
.nav > li.hover > ul , .nav li li.hover ul {
position: static;
}
}
步骤5:将悬停转换为Click
由于您还不能将鼠标悬停在触摸屏上,因此,我们将创建一些条件代码来检查窗口的宽度,然后编写代码来设置click()
和hover()
事件。
$(document).ready(function() {
var ww = document.body.clientWidth;
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
$(".nav li a").click(function() {
$(this).parent("li").toggleClass('hover');
});
} else {
$(".toggleMenu").css("display", "none");
$(".nav li").hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
}
});
对于click
事件,我们必须将目标元素从列表项更改为父项,因为列表是嵌套的,单击一个列表项也可以打开其子项。 但是,此更改的问题在于,单击锚标记将重新加载页面,但是我们不能阻止所有作为列表项后代的锚标记的默认行为。
为了解决这个问题,让我们添加一小段jQuery以将.parent
类.parent
到其子锚具有同级的任何列表项中,然后仅将这些锚定为目标(再次尝试保持标记的灵活性)。
$(".nav li a").each(function() {
if ($(this).next().length > 0) {
$(this).addClass("parent");
};
})
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
$(".nav li a.parent").click(function(e) {
e.preventDefault();
$(this).parent("li").toggleClass('hover');
});
} else {
... }
第6步:切换菜单
现在,我们的菜单在移动设备上看起来非常漂亮,但它占用了大量宝贵的屏幕空间。 一种流行的新方法是使用按钮切换导航列表,通常使用单词“ Menu”或菜单图标。 让我们的切换链接起作用,以便仅在单击时显示导航列表。
$(".toggleMenu").click(function(e) {
e.preventDefault();
$(".nav").toggle();
});
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
$(".nav").hide();
} else {
...
}
步骤7:更多样式
由于我们现在有了以类为目标的父级列表项,为什么不添加一点向下箭头以让我们的用户知道哪些列表项中有子级?
.nav > li > .parent {
background-position: 95% 50%;
}
.nav li li .parent {
background-image: url("images/downArrow.png");
background-repeat: no-repeat;
background-position: 95% 50%;
}
@media screen and (max-width: 800px) {
.nav > li > .parent {
background-position: 95% 50%;
}
.nav li li .parent {
background-image: url("images/downArrow.png");
background-repeat: no-repeat;
background-position: 95% 50%;
}
}
奖金:炫耀
现在,出于实际目的,此菜单效果很好。 如果在移动浏览器中打开它,将会得到一个非常有用的垂直手风琴列表,如果在桌面浏览器中打开它,则会得到一个漂亮的水平下拉列表。 但是,如果将桌面浏览器的大小调整为移动宽度,我们的导航仍然只能在悬停时使用,并且菜单不会通过切换功能隐藏。 对于大多数应用程序,这很好。 毕竟,您的普通网站访问者不会抓住他们浏览器的边缘,而是开始疯狂地来回拖动。
但是,如果您想给您的网络专业人员留下深刻的印象,那是行不通的。 我们需要设置脚本以响应resize
事件,并在将浏览器的大小调整到断点以下时执行条件代码。 扩展出色的“ 创建移动优先响应式设计”教程中演示的代码,我们将设置一些变量来跟踪和更新我们的浏览器宽度。
步骤8:窗口事件
因为我们要在页面加载和调整浏览器大小时都检查浏览器的宽度,所以让我们开始将所有条件代码移出$(document).ready()
事件并将其包装在功能adjustMenu
。
var ww = document.body.clientWidth;
$(document).ready(function() {
$(".toggleMenu").click(function(e) {
e.preventDefault();
$(".nav").toggle();
});
$(".nav li a").each(function() {
if ($(this).next().length > 0) {
$(this).addClass("parent");
};
})
adjustMenu();
});
function adjustMenu() {
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
$(".nav").hide();
$(".nav li a.parent").click(function(e) {
e.preventDefault();
$(this).parent("li").toggleClass('hover');
});
} else {
$(".toggleMenu").css("display", "none");
$(".nav li").hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
}
}
要在调整浏览器大小时调用该函数,我们将其bind
到窗口事件resize
和orientationchange
。 在此事件内,我们将重新定义ww
变量以适应浏览器的新宽度。
$(window).bind('resize orientationchange', function() {
ww = document.body.clientWidth;
adjustMenu();
});
在这一点上,我们引入了更多的问题:尽管起初看起来是可行的(水平菜单折叠到打开菜单的“菜单”按钮中),但很快可以看出我们有两个大问题:
- 如果我们重新调整移动宽度窗口的大小,使其超出断点,则整个菜单将消失。
- 悬停事件仍在移动版本上触发。
步骤9:显示和隐藏
我们缺少的导航菜单似乎很容易解决:只需在大于断点的条件下添加$("nav").show()
。 该解决方案似乎有效,但是带来了一些棘手的情况。 由于每次更改浏览器大小时都会重新评估代码,因此只要我们在打开菜单的情况下调整大小,它就会自动再次关闭。
这似乎不太可能出现,但移动浏览器却很奇怪:例如,在我的Galaxy S上,向下滚动然后返回到页面顶部会触发
resize
事件。 不好!
要解决此问题,我们需要采取某种方法来检查是否已单击菜单切换。 我将在菜单切换按钮上使用一个添加的类,因为它可以方便地进行样式设置(也许我们稍后需要向下箭头吗?)除了切换导航菜单的显示之外,菜单切换按钮现在将切换其自己的.active
类。 返回到比断点窄的条件,让我们更新代码以仅在菜单切换项没有类.active
情况下隐藏导航菜单。
$(document).ready(function() {
$(".toggleMenu").click(function(e) {
e.preventDefault();
$(this).toggleClass("active");
$(".nav").toggle();
});
});
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
if (!$(".toggleMenu").hasClass("active")) {
$(".nav").hide();
} else {
$(".nav").show();
}
$(".nav li a.parent").click(function(e) {
e.preventDefault();
$(this).parent("li").toggleClass('hover');
});
} ...
步骤10解除悬停事件的绑定
要解决我们的移动大小导航菜单对悬停事件做出响应的问题,我们只需要在比断点更窄的条件内将列表中的悬停事件unbind()
。
$(".nav li").unbind('mouseenter mouseleave');
但是,这带来了一个新问题:如果您将浏览器的大小从小调整到小,我们的click
事件将不起作用。 一些调试显示, click
事件已绑定到链接很多次,因此,一旦单击,将打开.hover
类,然后立即将其再次关闭。 发生这种情况是因为在调整窗口大小时,整个函数反复触发。 为了确保我们从正确的位置开始切换,我们需要先unbind
click事件,然后再重新绑定它。
但是,一旦将浏览器的大小重新设置为从小到大,我们现在就缺少了hover
事件,因为我们是在浏览器较小时取消绑定它的,而click
事件仍然存在,因此在绑定悬停语句之前也请取消绑定。 我们还将删除带有.hover
类的所有列表项,然后再将它们添加到hover事件中,以防止菜单随着扩展浏览器而尴尬地保持打开状态。
为了清楚起见,我正在使用.bind()
重写.hover()
.click()
和.hover()
事件。 这意味着同样的事情。
if (ww < 800) {
$(".toggleMenu").css("display", "inline-block");
if (!$(".toggleMenu").hasClass("active")) {
$(".nav").hide();
} else {
$(".nav").show();
}
$(".nav li").unbind('mouseenter mouseleave');
$(".nav li a.parent").unbind("click").bind("click", function(e){
e.preventDefault();
$(this).parent("li").toggleClass('hover');
});
} else {
$(".toggleMenu").css("display", "none");
$(".nav").show();
$(".nav li").removeClass("hover");
$(".nav li a").unbind("click");
$(".nav li").unbind('mouseenter mouseleave').bind('mouseenter mouseleave', function() {
$(this).toggleClass('hover');
});
}
万岁! 一切似乎都应该起作用。
步骤11:让IE表现良好
如果IE7没有出现崩溃,那将不是聚会,是吗? 我们这里有一个奇怪的错误,当子菜单显示在其他内容上时(在我们的示例中是一些lorem ipsum文本),子菜单消失了。 光标到达段落元素后,*不再*菜单。 我相当确定这是由于IE7处理position: relative;
的方式有些奇怪position: relative;
,通过在.nav a
元素上触发hasLayout
可以轻松解决此问题。
.nav a {
*zoom: 1;
}
进一步考虑
与往常一样,您必须对浏览器和功能支持做出自己的判断,但是诸如Modernizr和response.js之类的工具可能会因支持较旧的浏览器而有些痛苦。
我已经在Mobile Safari和我可以接触到的每个Android 2.3浏览器上测试了此菜单,它似乎运行得很好。 但是,此技术非常依赖JavaScript,并且由于某些移动浏览器(通常为Blackberry)对JavaScript的支持非常差,因此我们可能会给某些用户留下无法使用的导航菜单。
幸运的是,有许多方法可用于为无JavaScript的设备提供简化的布局。 可以想到将老式的.no-js
类添加到body标签,然后将其从JavaScript中删除的一种很好的老式技术,但是您也可以只为顶级导航项提供href
属性,将用户引导至常规“例如“鞋子”类别列表,并依靠preventDefault
来阻止启用JavaScript的设备中的此行为。
当然,媒体查询在旧版本的IE中将不起作用,因此您必须确定是否值得添加诸如fill.fill这样的polyfill来填补这一空白。
最后但并非最不重要的一点是,有一个令人讨厌的iOS错误,导致您在旋转设备时更改缩放级别。 请查看iOS Orientationchange Fix脚本来解决此错误。
进一步阅读
尽管此技术可能非常适合某些情况和菜单结构,但仍有许多其他选项可用于在移动设备上控制导航。 例如:
- Ryan DeBeasi的最新教程为单级导航菜单提供了一个聪明的解决方案。
- 浏览Brad Frost的综述响应式导航模式 ..
- 响应设计的复杂导航模式 。
- 对于移动优先解决方案,请查看HTML5 Rock的创建移动优先响应设计中的导航菜单。
随意阅读,克隆或分叉GitHub repo ,并感谢您的阅读!
响应式多级菜单 侧边菜单栏