创建响应式下拉菜单是一项棘手的任务。在设计和开发阶段,您必须做出许多决定。推荐的解决方案通常取决于您正在构建的网站或应用程序的特性。但是,对于所有类型的响应式下拉菜单,也有一些通用的最佳实践建议。
我在编写 如何使用 flexbox 构建响应式导航栏 教程时收集了这些最佳实践。由于它更像是一个动手教程,我没有太多的空间来解释必须做出哪些设计和开发决策——所以在这里。这些建议不仅适用于 flexbox 菜单,还适用于任何类型的响应式下拉菜单。
Fork This:响应式下拉菜单解决方案
查看我在 CodePen 上的响应式下拉菜单,它使用了我们在本指南中介绍的所有最佳实践。分叉并更改它以适合您自己的项目!
See the Pen Responsive Navigation Bar & Dropdown with Flexbox (updated, no jQuery) by Envato Tuts+ (@tutsplus) on CodePen.
1.使用移动优先设计
问题
尽管首先为桌面屏幕设计菜单然后使其适应较小的视口 似乎更容易,但通常情况并非如此——尤其是如果您的菜单具有下拉子菜单。
如果您首先为桌面设计菜单,它会很快变得复杂,最终您可能会在移动设备上得到一个非常长的菜单,用户只需不断滚动和滚动而不会到达终点!
解决方案
min-width您可以通过使用媒体查询而不是媒体查询来实现移动优先设计max-width:
@media alland (min-width: 960px) {// ...}
对于min-width媒体查询,移动设计将是默认设置,您将通过首先向上移动到平板电脑,然后到桌面屏幕来添加规则(或者,在某些情况下,您甚至不需要为平板电脑单独设计)。
从性能的角度来看,这也很重要。如果您使用移动优先设计,智能手机(经常通过计量数据连接或不完整的 wifi 访问您的网站)必须评估较少的样式规则,因为它们可以跳过媒体查询块。
2.重新排序菜单项
问题
在许多情况下,您可能希望在移动设备、平板电脑和桌面屏幕上以不同的顺序显示菜单和子菜单项,但希望保持 html 大纲的逻辑性和可访问性。
解决方案
使用该 order属性,您可以更改菜单项的视觉顺序,同时保持 dom 不变。
根据经验,HTML 大纲中的菜单项应遵循最适合屏幕阅读器和其他辅助技术用户的顺序。不要忘记搜索引擎机器人也类似于屏幕阅读器,因为它们只看到 HTML 大纲,但看不到视觉顺序。
这是一个由号召性用语按钮和菜单链接组成的菜单示例。我们希望号召性用语 (CTA) 出现在移动设备和平板电脑菜单的开头以及桌面菜单的末尾。在 HTML 中,菜单链接将位于首位(但是,这不是必须的,因为在某些情况下,您可能希望将号召性用语按钮放在屏幕阅读器用户的首位)。
<nav>
<ul>
<li class="mlink">Menu Link 1</li>
<li class="mlink">Menu Link 2</li>
<li class="mlink">Menu Link 3</li>
<li class="cta">CTA 1</li>
<li class="cta">CTA 2</li>
</ul>
</nav>
由于默认值为order0,因此您不必为第一个元素更改它。对于视觉顺序中的第二个元素,其值为 1。对于第三个元素,其值为 2,依此类推。注意也order可以取负值。因此,css 将如下所示:
ul {
display: flex;
}
.mlink {
order: 1;
}
@media all and (min-width: 960px) {
.mlink {
order: 0;
}
}
注意:该order属性仅适用于 flexbox 和 CSS 网格布局,因此如果您想使用它,您需要添加display: flex;或添加display: grid;到父菜单项。
3. 在手机和平板电脑上使用事件监听器
问题
在桌面上,子菜单通常使用:hover伪类激活。当用户悬停父菜单项时,屏幕上会弹出相关的子菜单。当他们将鼠标移开时,子菜单就会消失。但是,:hover在移动设备、平板电脑和其他触摸设备上没有。
解决方案
通过使用 javascript 事件***器,您可以在用户单击/触摸父菜单项时打开和关闭子菜单。该addeventListener() 方法是 DOM api 的一部分,并内置于从 IE9开始的每个现代浏览器中。
以下代码示例来自我上面提到的 flexbox 菜单教程,它会.submenu-active在用户单击父菜单项时添加或删除类。的display样式.submenu-active 可以用 CSS 添加。
/* Activate Submenu */
function toggleItem() {
if (this.classList.contains("submenu-active")) {
this.classList.remove("submenu-active");
} else if (menu.queryselector(".submenu-active")) {
menu.querySelector(".submenu-active").classList.remove("submenu-active");
this.classList.add("submenu-active");
} else {
this.classList.add("submenu-active");
}
}
/* Event listeners */
for (let item of items) {
if (item.querySelector(".submenu")) {
item.addEventListener("click", toggleItem, false);
}
}
for...of循环遍历所有菜单项,然后该if块选择具有子菜单的菜单项并向它们添加单击事件***器。toggleItem() 每当用户单击/点击带有子菜单的菜单项时,都会调用自定义 函数。
DOM API 中有许多事件可以使用事件***器作为目标。对于触摸设备,click是touchstart最常用的。上面,我们使用click了它,因为它同时适用于点击和触摸,而touchstart只适用于触摸而不是点击。
注意:您只能使用该 addEventListener()方法定位一种事件类型。与 jquery 相比,这是一个重要的区别,您可以将多个事件添加到on()用于事件***器的方法中。
4.在桌面上单击和悬停之间进行选择
问题
既然您的子菜单可以在移动设备和平板设备上运行,那么还有一个问题:在桌面设备上,您可能希望让用户在悬停时而不是单击时显示子菜单。但是,如果您已经添加了单击事件***器以在触摸设备上显示子菜单,则桌面菜单将对单击和悬停做出反应。
这两个用户操作很容易相互干扰,这可能会导致混乱,或者在某些情况下,甚至会破坏菜单的布局。
解决方案
在桌面视口上,您需要决定子菜单是否会在悬停或单击时显示。一般来说,我会说如果你正在构建一个网络应用程序,请选择点击,因为这是大多数网络应用程序用户会寻找的行为。而且,如果您正在构建网站,请选择悬停,因为它在网站上感觉更自然。
如果您选择点击
如果您决定保留桌面的单击事件处理程序,则需要修复一个小的 UX 问题。当用户离开菜单区域时,子菜单不会自行关闭(这是悬停时的正常行为)。要关闭子菜单,用户必须导航回父菜单项并再次单击它,这不是理想的用户体验。
要解决此问题,您可以使用以下脚本,让用户通过单击页面上的任意位置来关闭子菜单。
/* Close Submenu From Anywhere */
function closeSubmenu(e) {
let isClickInside = menu.contains(e.target);
if (!isClickInside && menu.querySelector(".submenu-active")) {
menu.querySelector(".submenu-active").classList.remove("submenu-active");
}
}
/* Event listener */
document.addEventListener("click", closeSubmenu, false);
它向对象添加一个事件***器,并 在文档内部但在菜单区域之外发生单击时 document删除 该类。.submenu-active
如果您选择悬停
如果选择悬停,则需要使用 JavaScript 检测视口大小,if并向事件***器添加一条额外语句,以检查视口的宽度是否小于媒体查询断点。您可以使用对象的clientWidth属性document来检测视口的宽度(不包括任何滚动条)。
所以,在上面的代码中,调用自定义toggleItem()函数的事件监听循环会发生如下变化:
if (document.documentElement.clientWidth < 960) {
for (let item of items) {
if (item.querySelector(".submenu")) {
item.addEventListener("click", toggleItem, false);
}
}
}
在 CSS 中,您需要将负责桌面子菜单布局的规则添加到:hover伪类中。
5. 对空父菜单项使用不带 href 属性的 <a> 标记
问题
很多时候,父菜单项仅用于打开和关闭所属的子菜单,但它们不会链接到任何地方。但是,如果您想保持 HTML 大纲一致且 CSS 样式表简单,您还需要将<a href="#">锚标记添加到这些“空”菜单项。
但是,在这种情况下,当用户单击父菜单项以打开或关闭子菜单时,页面将重新加载并跳转到移动设备的顶部。如果您的菜单很长,这尤其糟糕。
解决方案
此问题的常见解决方案是将 "javascript: void(0);"值添加到href属性中。但是,这被认为是一种不好的做法,因为它使用伪 URLundefined作为值返回,这可能导致不同的错误和意外行为。
处理此问题的最简单方法是 对这些空菜单项使用<a> 不带属性的标记 ,这是一种有效的解决方案,因为它不是必需的属性。根据W3C 文档:hrefhref
“和 元素href上的属性不是必需的;当这些元素没有属性时,它们不会创建超链接。”aareahref
<a>但是,空标签有两个问题:
它们不能通过键盘访问,因为它们在默认的制表符顺序中被省略了。您可以通过将tabindex="0"属性添加到每个 <a>没有属性的标签来解决此问题href。
即使<a>标签顺序中包含空元素,用户也无法通过按键盘上的Enter来打开/关闭相关的子菜单。这可以通过为事件创建事件***器来解决keypress。
因此,这就是 HTML 的结构:
<ul class="menu">
<li class="item"><a href="home.html">Home</a></li>
<li class="item"><a href="about.html">About</a></li>
<li class="item has-submenu">
<a tabindex="0">services</a>
<ul class="submenu">
<li class="subitem"><a href="service1.html">Service 1</a></li>
<li class="subitem"><a href="service2.html">Service 2</a></li>
<li class="subitem"><a href="service3.html">Service 3</a></li>
</ul>
</li>
<li class="item has-submenu">
<a tabindex="0">Plans</a>
<ul class="submenu">
<li class="subitem"><a href="plan1.html">Plan 1</a></li>
<li class="subitem"><a href="plan2.html">Plan 2</a></li>
<li class="subitem"><a href="plan3.html">Plan 3</a></li>
</ul>
</li>
<li class="item">li><a href="blog.html">Blog</a></li>
<li class="item"><a href="contact.html">Contact</a></li>
</ul>
对于事件***器,您可以使用toggleItem()我们为打开/关闭子菜单创建的相同自定义函数。您只需通过以下方式将 keypress事件***器添加到上述 for...of循环中:
for (let item of items) {
if ( item.querySelector(".submenu")) {
item.addEventListener("click", toggleItem, false);
item.addEventListener("keypress", toggleItem, false);
}
}
6. 使图标离线可用
问题
如果使用 font Awesome 等在线图标字体库将图标添加到下拉子菜单中,例如常用的向下箭头,则在离线访问网站/应用时图标会消失。请注意,离线可用性也是一个可访问性问题。
解决方案
这个问题的解决方案相对容易。您也可以在本地加载它们,而不是从 CDN 加载图标库。例如,这是您自己托管 Font Awesome的方法。如果您只加载您在网站上使用的库的一部分,您甚至可以节省页面加载——即使这也取决于特定图标库的结构。
这就是响应式下拉菜单最佳实践!
构建响应式下拉子菜单看似简单,但是需要注意很多细节。它需要在不同的设备上工作,对不同的事件做出正确的反应,键盘用户可以访问,离线可用等等。
如果您仔细考虑您的网站或应用程序通常会被访问的用例,则更容易决定如何解决用户尝试使用您的子菜单时可能出现的所有问题。
- 问题
- 解决方案
- 问题
- 解决方案
- 问题
- 解决方案
- 问题
- 解决方案
- 如果您选择点击
- 如果您选择悬停
- 问题
- 解决方案
- 问题
- 解决方案