在响应式设计方面,我们面临着各种技术,这些技术可以帮助我们最佳地处理如何为小屏幕更改导航菜单。 资源似乎无穷无尽。 这就是我要向您展示四个主要概念以及讨论所有概念的优点和缺点的原因。
其中三个是用纯 CSS 制成的,一个使用一行 JavaScript。
开始之前
在本篇文章中提供的代码中,我没有使用任何供应商前缀,以使 CSS 更易于查看和理解。 更复杂的 CSS 示例使用 SCSS。 每个示例都托管在 CodePen 上,如果您愿意,您可以在那里看到编译后的 CSS。
本文中的所有菜单概念都基于这种简单的 HTML 结构,我称之为 **基本菜单**。 role 属性 用于指定特定概念(全水平、选择、自定义下拉菜单和画布外)。
<nav role="">
<ul>
<li><a href="#">Stream</a></li>
<li><a href="#">Lab</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
为了解决小屏幕问题,我在所有概念中都使用相同的 媒体查询。
@media screen and (max-width: 44em) {
}
1. 全水平
这是一种最简单的方法,因为您只需要在小屏幕上使 列表元素 充满整个宽度。
<nav role="full-horizontal">
<ul>
<li><a href="#">Stream</a></li>
<li><a href="#">Lab</a></li>
<li><a href="#">Projects</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
@media screen and (max-width: 44em) {
nav[role="full-horizontal"] {
ul > li {
width: 100%;
}
}
}
这是它在小屏幕上使用自定义样式的样子。

优点
- 无需 JavaScript
- 无需额外 HTML
- 简单 CSS
缺点
- 占用过多屏幕空间
演示
2. 选择
这个概念在小屏幕上隐藏了基本菜单,并显示了一个 select 菜单代替。
为了实现这一点,我们需要扩展基本标记并添加一个 select。 为了使 select 工作,我们还添加了一些 JavaScript,它在 select 上的 onchange 事件发生时更改 window.location.href。
<nav role="select">
<!-- basic menu goes here -->
<select onchange="if (this.value) window.location.href = this.value;">
<option value="#">Stream</option>
<option value="#">Lab</option>
<option value="#">Projects</option>
<option value="#">About</option>
<option value="#">Contact</option>
</select>
</nav>
我们在较大屏幕上隐藏 select。
nav[role="select"] {
> select {
display:none;
}
}
在小屏幕上,我们隐藏基本菜单并显示 select。 为了帮助用户识别这是一个菜单,我们还在添加一个带有“Menu”文本的 伪元素。
@media screen and (max-width: 44em) {
nav[role="select"] {
ul {
display: none;
}
select {
display: block;
width: 100%;
}
&:after {
position: absolute;
content: "Menu";
right: 0;
bottom: -1em;
}
}
}
这是它在小屏幕上使用自定义样式的样子。

优点
- 不需要太多空间
- 使用原生控件
缺点
- 需要 JavaScript
- 重复内容
- 并非所有浏览器都支持 select 的样式设置
演示
3. 自定义下拉菜单
这个概念在小屏幕上隐藏了基本菜单,并显示了一个 input 和 label(用于 Checkbox Hack)代替。 当用户点击标签时,基本菜单显示在其下方。
<nav role="custom-dropdown">
<!-- Advanced Checkbox Hack (see description below) -->
<!-- basic menu goes here -->
</nav>
Checkbox Hack 的问题
默认 Checkbox Hack 存在两个问题
- 在移动 Safari(iOS < 6.0)上不起作用。 由于错误,在 iOS < 6.0 上无法点击标签以切换输入。 唯一 解决方法 是在标签中添加一个空 onclick。
- 在默认 Android 浏览器(Android <= 4.1.2)上不起作用。 曾经有一个 WebKit 相邻/通用兄弟 & 伪类 错误,它阻止了伪类与 相邻 (+) 或 通用 (~) 兄弟组合器一起使用。
h1 ~ p { color: black; }
h1:hover ~ p { color: red; }
这没有影响,因为 checkbox hack 使用了伪类 :checked 与通用兄弟一起使用。 由于此问题已在 WebKit 535.1(Chrome 13)中 修复,而 Android 4.1.2 上的实际 WebKit 是 534.30,因此正常的 checkbox hack 在迄今为止的任何 Android 设备上均不起作用。
最佳 解决方法 是在 body 元素上添加一个仅限 WebKit 的虚假动画。
所有这些组合创造了 **高级 Checkbox Hack**
<!-- Fix for iOS -->
<input type="checkbox" id="menu">
<label for="menu" onclick></label>
/* Fix for Android */
body {
-webkit-animation: bugfix infinite 1s;
}
@-webkit-keyframes bugfix {
from { padding: 0; }
to { padding: 0; }
}
/* default checkbox */
input[type=checkbox] {
position: absolute;
top: -9999px;
left: -9999px;
}
label {
cursor: pointer;
user-select: none;
}
对于大屏幕,我们隐藏标签
nav[role="custom-dropdown"] {
label {
display: none;
}
}
对于小屏幕,我们隐藏基本菜单并显示标签。 为了帮助用户识别这是一个菜单,我们还在标签中添加了一个带有“≡”文本(转换为“\2261”以用作伪元素上的内容)的伪元素。 当用户点击输入时,基本菜单显示,列表元素扩展到整个宽度。
@media screen and (max-width: 44em) {
nav[role="custom-dropdown"] {
ul {
display: none;
height: 100%;
}
label {
position: relative;
display: block;
width: 100%;
}
label:after {
position: absolute;
content: "\2261";
}
input:checked ~ ul {
display: block;
> li {
width: 100%;
}
}
}
}
这是菜单在小屏幕上使用自定义样式的样子。


优点
- 关闭时不需要太多空间
- 自定义样式
- 无需 JavaScript
缺点
- 语义不佳(输入/标签)
- 额外 HTML
演示
4. 画布外
这个概念在小屏幕上隐藏了基本菜单,并显示了一个 HTML 输入和标签(用于高级 Checkbox Hack,有关更多信息,请参阅 3. 自定义下拉菜单)代替。 当用户点击标签时,基本菜单从左侧飞入,内容移到右侧 - 屏幕被分成两部分:菜单约 80% 和内容约 20%(取决于分辨率和 css 单位)。
<input type="checkbox" id="menu">
<label for="menu" onclick></label>
<!-- basic menu goes here -->
<div class="content">
<!-- content goes here -->
</div>
在较大屏幕上,我们隐藏标签。
label {
position: absolute;
left: 0;
display: none;
}
在小屏幕上,我们将基本菜单隐藏在视窗之外并显示标签/输入。 为了隐藏菜单,我们指定一个宽度($menu_width)并为其添加一个负位置。 为了帮助用户识别这是一个菜单,我们还在标签中添加了一个带有“≡”文本(转换为“\2261”以用作伪元素上的内容)的伪元素。
当用户点击输入时,基本菜单从左侧飞入,内容移到右侧。
@media screen and (max-width: 44em) {
$menu_width: 20em;
body {
overflow-x: hidden;
}
nav[role="off-canvas"] {
position: absolute;
left: -$menu_width;
width: $menu_width;
ul > li {
width: 100%;
}
}
label {
display: block;
}
label:after {
position: absolute;
content: "\2261";
}
input:checked ~ nav[role="off-canvas"] {
left: 0;
}
input:checked ~ .content {
margin-left: $menu_width + .5em;
margin-right: -($menu_width + .5em);
}
}
这是菜单在小屏幕上使用自定义样式的样子。


优点
- 关闭时不需要太多空间
- 自定义样式
- 无需 JavaScript
- 来自 Facebook/Google+ 应用程序的约定
缺点
- 语义不佳(输入/标签)
- 额外 HTML
- 对 body 的绝对位置 = 感觉像是固定位置
演示
它在 IE 上有效吗?
上面使用的所有技术都具有一个目标:为现代浏览器创建响应式菜单! 并且由于任何移动设备上都没有 IE 8 或更低版本,因此我们无需担心它。
感谢您撰写这篇文章,不过我有一点意见
您可以使用 :target 伪类,而不是使用复选框技巧
查看 Raphael Goetter 的实验结果:http://thinkmobilefirst.net/nav/
我不知道特定的设备限制,我很乐意收到关于此方面的反馈,我刚刚在以下网站上部署了它:
http://www.rescue2014.fr(当然,您可以调整浏览器大小)
:target 非常适合语义,但缺点是它会添加历史记录项(影响后退按钮)。
:checked 在语义上不如 :target 好,但在功能上更好。
情况很棘手。
在这种情况下,我倾向于放弃语义。可能只有我们才会注意到并真正关心语义,而非语义版本效果更好。所以,我倾向于只使用有效的版本。网站的普通用户可能不会看到代码,即使他们看到了,他们可能也不在乎语义。
@cnwtx
没错,但是更多面向可访问性的用户代理(屏幕阅读器等)依赖语义来查找用户想要显示/读取的网站元素。
@Ando,没错,但我倾向于认为屏幕阅读器等会倾向于看到
@cwntx
是的,说实话,我不太确定屏幕阅读器在
很棒的文章,Tim!非常清晰,并提供了多种方法来完成同一件事,我喜欢它。
顺便说一下,这个复选框技巧的补充非常棒,我想知道你怎么会想到这样的东西!
在各种平台上进行了数小时的反复试验,借助 BrowserStack 的帮助。我只想让它在所有地方都能正常工作。
我认为使用 role 属性不是一个好主意,您可以使用 data-* 属性来代替它。
http://www.w3.org/TR/xhtml-role/
http://ejohn.org/blog/html-5-data-attributes/
我会考虑使用 data- 而不是 role-属性!谢谢。
我完全同意;这是我第一眼就注意到的东西。
role
应该(以机器可读的方式)定义元素的目的,但在这里,您使用它来定义元素的呈现方式。如 @Israel 所建议,使用
data-*
属性(甚至使用class
)会更合适。很棒的汇总!将来我可能会在这里访问 1000 次。
这些样式分组似乎可以基于一个简单的 HTML 类,而不是 role 属性。role 属性通常(但并非总是)保留给 ARIA 角色,这些角色本身已经很令人困惑,但它们是一组预定义的角色,对其他机器有意义。
我不知道 role 属性是为 ARIA 角色保留的。感谢您的信息!
严格来说,它并没有被保留,但它对 ARIA/屏幕阅读器等起到了作用。您所做的事情从技术上来说并没有错,因为您只是根据属性进行了选择,但我相信其他人会建议您使用 HTML5 的“data-”属性方法,因为它专门用于处理您正在寻找的内容。使用 HTML5 自定义数据属性时,需要注意的是,插件/库代码可能会在您不知情的情况下使用与您相同的命名约定。一个标准的命名约定,如 img src=”” data-index=”0″ alt=”Bobs Hair”,可能会在您的应用程序中发生冲突,因此请注意您的命名约定。
这里有一些很棒的解决方案,给了我很多想法和我想尝试的东西。
以下是我一直在研究的一种方法,它不需要 JavaScript,但使用 JavaScript 会更好一些。所有内容都在 GitHub 上,所以如果您想使用/修改它,请随意使用:)
martinblackburn.github.com/responsive-nav/
当使用复选框技巧时,为了解决 iOS 问题,我只需在开头使用
(-prefix-)appearance: none;
来覆盖默认样式,对<input>
进行样式设置。iOS 问题不是关于样式。它会阻止您单击
label
来切换input
(选中/未选中)。如果您想修复此问题,可以在label
中添加一个空的onclick
,或者将input
(具有完整的高度/宽度)放在标签的前面。我想说的是,如果您对
input
进行样式设置,则甚至不需要标签。我知道这不是关于样式。您仍然可以使用::before
来显示图标,甚至可能使用隐藏的label
来进行 SEO,但这将消除对那个空的onclick
的需求。现在我明白你的意思了。这听起来像是一个很酷的改进,可以摆脱标签。但不幸的是,它在 最新的 Opera 或 Internet Explorer 10 中不受支持。
确实如此,但我认为通过添加
border
,默认外观也会被覆盖(我知道对于文本输入是这样)。@Martin:我已经做了一件非常类似的事情,这是我最喜欢的做法。但文章中的最后一个(画布外)也很不错,我想我会把它们结合起来,通常我都会避免使用表单元素进行导航。
另一个选择是,如果您不介意使用一点 JavaScript,可以使用 Brad Frost 的切换方法来代替复选框技巧。需要 JavaScript,但支持良好。我在 CodePen 上做了一个演示 on CodePen。
非常棒,谢谢各位。
Glenn
太棒了,我一直在学习,非常感谢
做得不错,感谢分享。
无论如何,我认为标签、输入或选项在语义上不适合导航。
为什么使用 JavaScript 以及在导航中添加类会有什么问题?为什么它不占优势?简单的标记、干净的 CSS、简单的 JS。
我也有同样的看法。我认为使用类和简单的 JavaScript 来代替表单元素会更好。
使用 JavaScript 的唯一缺点是,当禁用 JavaScript 时,它将无法正常工作。
如文章所述,添加标签/输入在语义上是不正确的。这只是在不使用 JavaScript 的情况下处理此类菜单的一种方法。但您可以根据自己的喜好扩展这些概念!请记住与社区分享。
我同意 JavaScript 是一个很好的解决方案。它可以避免表单元素和/或重复内容的混乱。如果您是先进行移动端设计——从那些没有 JavaScript 的用户可以接受的标记和功能开始——那么您就可以开始了。
对于非常大或复杂的菜单,我的解决方案是将菜单放在单独的页面中。没有 JavaScript 的用户单击“菜单”按钮会跳转到单独的菜单页面。对于那些支持 JS 的用户,我会使用 ajax 加载菜单,然后使用 JavaScript 来适当地隐藏和显示菜单。
我认为目前最好的方法是第一个选项,即完整水平。
是的,如果您有很多顶级导航项目,它可能会占用大量的屏幕空间,但它是唯一没有其他缺点的选项。
也许将来我们会拥有更好的语义上正确的选项,在此之前,我认为这种方法是最安全的。
不错的文章,Tim!
正如 Dave 在上面所写,这篇文章提醒我们在创建新的响应式菜单时要牢记这些内容。
可以添加子菜单吗?
Chris,能够调整 Codepen iframe 的大小会很有趣,这样我们就可以看到媒体查询的实际效果,而无需在另一个标签页中打开,然后调整浏览器大小。
这很有趣也很有用。
很棒的菜单!:)
有没有办法将这个 SCSS 转换成普通的 CSS?
嗨,如果你点击 Codepen 中显示“SCSS”的地方,它会编译成 CSS。
嗨,文章写得很好!
从一开始,这篇文章似乎是针对桌面优先的,并使用移动端修改来使小屏幕正常显示。
这是否比移动端优先方法更好,所有修改使桌面代码性能更好?谢谢!
我选择使用“选择”菜单,并使用“完整水平”菜单作为备选,使用 JavaScript 显示“选择”菜单并隐藏“完整水平”菜单。
这些示例在我的 iPhone 上不起作用,我只看到“全尺寸”页面。
这是 Codepen 的限制吗?它不允许你设置视口元标签?
对我来说,最好的解决方案仍然是 1 和 3 的组合,或者你甚至可以做 1 和 4。这意味着你有一个默认情况下在页面顶部(或底部)可见的菜单,并在页面使用 JS 加载时将其转换为其他解决方案之一。然后,你可以使用 JS 事件进行触发,而无需依赖于复选框黑客,这似乎仅仅是一个黑客。感谢这篇文章!
Tim,写得很好!再次感谢你帮助我完成我一直在开发的导航菜单插件。
完美的分类和比较,一如既往!
谢谢!
如上面 @Vivek Nath.R 所述,所有示例都没有处理子导航。虽然我总是喜欢构建不需要任何子导航的网站,但这对于某些客户(或网站)来说是不可能的。响应式导航的一个重大挑战是如何处理大型菜单。Brad Frost 在 这里 提供了一些很棒的示例。但这是一篇很棒的文章,很高兴看到 CodePen 被很好地使用。
也许在文章标题中包含“移动”这个词会更好。
文章解释得很棒,内容也很详细。我一直在寻找这个,而且我一定会使用自定义下拉菜单方法。
PS。分享链接在哪里?
如何制作一个在 PC 上使用悬停进行下拉,而在移动设备上使用点击的菜单?