两行 CSS 代码实现从移动设备到桌面的菜单
作为一名前端开发者,多年来我创建过无数菜单。这些菜单通常是网站中最复杂的元素,涵盖了移动端交互、桌面端过渡、项目重新排序等等。有一次,我为一个客户创建了一个超级菜单,它非常庞大,以至于用户误以为它是整个网页,因为它覆盖了整个页面。
然而,在我最近的项目中,我利用了一些最新和最强大的 CSS 功能来达到简单的目的。
让我们开始吧!
结构
对于标记,我能想到的最简单的结构是这样的,它仍然支持从移动弹出菜单到桌面菜单所需的灵活性:
<header>
<a href="/">LOGO</a>
<label>
<input type="checkbox">
</label>
<div class="menu-flyout">
<nav class="menu-main">
<a href="#">...</a>
<a href="#">...</a>
</nav>
<nav class="menu-cta">
<a href="#">CTA</a>
<a href="#">CTA</a>
</nav>
</div>
</header>
我们稍后会添加更多类和其他内容,但这是基本结构。此版本无需任何 JavaScript即可运行,但存在可访问性问题,我们稍后也会研究。
主要元素<header>
是一个flex
容器,用于justify-content: space-between
将徽标放置在左侧,将切换器放置在右侧:
.menu-flyout
是两个导航块的容器:一个带有主菜单项,另一个带有 CTA(号召性用语)。
当可见时,弹出窗口将覆盖整个屏幕,否则将放置在屏幕外:
.menu-flyout {
inset: 0;
position: fixed;
translate: -100vw 0;
}
导航块都是设置为自定义属性的flex
容器:flex-direction
flex-direction: var(--menu-flyout-dir, column);
第二个导航块位于底部,使用justify-content: end
:
切换器只是一个样式<input type="checkbox">
。我们可以使用它来在点击时显示弹出窗口:
header:has(input:checked) .menu-flyout {
translate: 0;
}
由于切换器包裹在标签中,我们可以使用它将其隐藏在桌面上:
@media (min-width: 768px) {
label { display: none; }
}
注意:不要担心,最终示例中的所有内容都会有类,我使用普通标签只是为了简化示例!
到目前为止一切顺利。当我们将屏幕调整到桌面大小时,我们得到:
好的,所以<label>
带有切换器的 按照预期被隐藏了,<header>
仍然是一个flex
容器,弹出窗口仍然位于屏幕外,但现在是巨大的,占据了整个屏幕空间。
让我们用两行 CSS 来解决这个问题:
@media (min-width: 768px) {
.menu-flyout {
--menu-flyout-dir: row;
display: contents;
}
}
其结果是:
耶!如果我们检查一下,就会发现这两个导航块现在是主flex
容器中的“直接”项,而且.menu-flyout
似乎消失了:
那么到底发生了什么?
display: contents
这相当于对一个元素说:“忘掉你自己的盒子,只显示你的子节点”。或者用正式的 MDN 术语来说:
这些元素本身不会生成特定的框。它们被其伪框及其子框所取代。
我们通过更新之前声明的自定义属性来更改两个导航块的 flex-direction --menu-flyout-dir
。
我们现在有一个可用的、从移动到桌面的菜单,它只使用很少的HTML 和 CSS,根本不使用 JavaScript 。
让我们看看还能做什么。我已将桌面最大宽度设置为1200px
,但我想将其“拉伸”background-color
到屏幕边缘。
这曾经需要在菜单周围添加一个额外的元素,但现在可以用一个非常大的元素来实现border-image
:
border-image: conic-gradient(
hsl(240, 10%, 20%) 0 0)
fill 0//100vw;
好的...这实际上会覆盖整个屏幕。
让我们添加一个clip-path
来解决这个问题:
clip-path: polygon(
-100vw 0,
100vw 0,
100vw 100%,
-100vw 100%
);
现在,即使在非常大的桌面上,背景也会延伸到屏幕边缘(您可能需要放大才能看到它!):
移动修复
在移动设备上,我们可以使用它:has
来检测切换按钮何时被选中,甚至可以从<body>
-element 元素中检测。当弹出窗口可见时,我们可以利用此功能来防止溢出/滚动:
@media (max-width: 767px) {
body:has(.menu-toggle:checked) {
overflow: hidden;
}
}
如果您旋转(大)手机,菜单将切换到桌面版本。
如果你的 iPhone 有“刘海”,菜单就会“进入”刘海里。我们可以使用env()
-function 和 来解决这个问题safe-area-inset
。首先,我们创建两个分别带有 block- 和 inline-padding 的变量:
header {
--menu-pb: .75em;
--menu-pi: 1.5em;
}
...稍后我们将定义填充:
header {
padding:
var(--menu-pb)
calc(env(safe-area-inset-right)
+ var(--menu-pi))
var(--menu-pb)
calc(env(safe-area-inset-left)
+ var(--menu-pi));
}
现在,当您旋转手机时,如果手机有凹口,则会在线添加额外的填充!
演示
可访问性问题
虽然上面的菜单无需任何 JavaScript即可正常工作,但切换按钮复选框是一种黑客技术,无法与屏幕阅读器很好地配合使用。
让我们id
向弹出窗口添加一个,并将替换<label>
为:
<button
class="menu-toggle"
aria-label="Toggle flyout menu"
aria-expanded="false"
aria-controls="flyout-menu">
</button>
然后添加一小段 JS 代码:
const toggle = document.querySelector(
'.menu-toggle');
toggle.addEventListener('click', () => {
toggle.setAttribute(
'aria-expanded', document.body
.classList.toggle('menu-open')
);
})
menu-open
这段代码切换了body 元素的class 。我们将使用状态(无论 class 是否设置)作为aria-pressed
-attribute。
我们之前使用过的所有地方:checked
,现在都应该使用menu-open
:
.menu-open .menu-flyout { translate: 0; }
演示
演示中涉及的内容(过渡、夹紧间隙、悬停等)比我在本文中展示的要多得多。
在 Codepen 上打开它们以查看全宽桌面视图 - 分叉它们 - 并使用它们进行操作。
请在评论中告诉我你的想法。
摄影:Brett Sayles
鏂囩珷鏉ユ簮锛�https://dev.to/madsstoumann/mobile-to-desktop-menu-in-2-lines-of-css-4ikn