发布于 2026-01-06 2 阅读
0

让我们从零开始构建一个响应式导航栏。Result DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

让我们从零开始构建一个响应式导航栏。

结果

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

今天,我们将使用 HTML、CSS、clip-path、flexbox、网格布局等技术,从零开始构建一个响应式导航栏、一个动画汉堡图标和一个漂亮的展开效果……

阅读全文或在 YouTube 上观看我的编程演示:

结果

空白 HTML5 文档

让我们从一个空白的HTML5文档开始。这是我们的起点,我们将在接下来的章节中逐步扩展它。

你知道在 VS CODE 中输入感叹号 (!) 并按 Tab 键会生成一个空白的 HTML5 文档吗?

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Navbar</title>
  </head>
  <body>
    <!-- content will go here -->
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

使用样式和外部字体

我们需要在文档中包含三个(或多或少)外部资源<head>

  1. 我们自己的样式表在styles.css
  2. 我们将使用Nunito字体作为页面的默认字体。因此,该字体通过 Google Fonts 加载。我们将使用 200 和 400 两种字重。
  3. Font Awesome 可通过 [此处应填写链接cdnjs.com] 获取。请注意 [integrity此处应填写属性名称] 属性,因为它是一项重要的安全功能。如果用户代理下载的文件的 SHA 校验和与 [此处integrity应填写属性名称] 属性中指定的 SHA 校验和不同,浏览器就会知道下载的内容已被篡改,因此会拒绝访问。有关子资源完整性的更多信息,请参阅这篇 MDN 文章
<head>
  ....

  <!-- or own styles -->
  <link rel="stylesheet" 
    href="styles.css"
    type="text/css" />

  <!-- "Nunito" font via google fonts -->
  <link rel="preconnect" 
    href="https://fonts.gstatic.com" />
  <link rel="stylesheet"
    href="https://fonts.googleapis.com/css2?family=Nunito:wght@200;400&display=swap" >

  <!-- Font Awesome via CDNJS  -->
  <link rel="stylesheet" 
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" 
    integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" 
    crossorigin="anonymous" />
  </head>
Enter fullscreen mode Exit fullscreen mode

变量和基本样式

现在,styles.css让我们从文件中的一些基本内容开始。例如,变量。将我们将要使用的颜色以及一些常见的重复性内容(例如过渡设置)放在变量中,对于自定义非常有用。

请注意该nav-height变量,因为它允许我们控制导航栏的高度,许多其他元素的位置和大小都将取决于该变量。

:root {
  --fg-color: rgba(255, 255, 255, 0.9);
  --bg-color: #2b2b2b;
  --highlight-primary: #008aff;
  --gradient: 
    linear-gradient(300deg, #ba4aff, rgba(186, 74, 255, 0) 70%), 
    linear-gradient(227deg, #008aff, rgba(0, 138, 255, 0) 70%),
    linear-gradient(104deg, #00ffc6, rgba(0, 255, 198, 0) 74%);

  --nav-height: 3rem;

  --transition: 250ms ease-out;  
  --transition-long: 500ms ease-out; 
}
Enter fullscreen mode Exit fullscreen mode

该块会重置每个元素padding从而为浏览器和元素带来极大的一致性。marginbox-sizing

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

在根<html>元素上,我们定义了基本文本颜色和基本字体,包括字体大小。这一点非常重要,因为我们将使用很多rem值,而这些值始终与根元素上设置的字体大小相关<html>


html {
  font-family: "Nunito", sans-serif;
  font-size: 18px;
  font-weight: 200;
}
Enter fullscreen mode Exit fullscreen mode

<body>只是一个弹性容器,可以将内容水平居中,并将内容置于最顶部。min-height: 100vh它确保主体始终至少占据整个视口高度,从而使背景颜色覆盖整个页面。


body {
  padding-top: var(--nav-height);
  min-height: 100vh;

  background: var(--bg-color);
  color: var(--fg-color);
}
Enter fullscreen mode Exit fullscreen mode

头部标记和 CSS 设置

我们先从一个简单的<header>标签开始……

<header>
</header>
Enter fullscreen mode Exit fullscreen mode

……它将被定位fixed,也就是说,无论文档中的滚动位置如何,它始终将具有通过 和 提供topleft位置right

header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--nav-height);
  font-size: 1.5rem;

  background: var(--bg-color);
  color: var(--fg-color);

  box-shadow: -2px 2px 8px 0px rgb(0 0 0 / 80%);

  border-bottom: 1px solid var(--highlight-primary);

  z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

为了管理页眉内的内容流,我们将使用 CSS 网格,它会自动为每个元素生成一个新列grid-auto-flow: column。将 `grid` 属性设置为 `true`grid-auto-columnsmax-content告诉 CSS 网格,每个单元格都会围绕每个元素的内容整齐排列,而不会强制换行。

header {
  ...

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
}
Enter fullscreen mode Exit fullscreen mode

标志

左侧会显示一个标志,它其实就是一个来自 Font Awesome 的羽毛图标:

<header>
  <div class="logo">
    <i class="fas fa-feather-alt"></i>
  </div>
</header>
Enter fullscreen mode Exit fullscreen mode

该功能的样式将如下所示:

  • 它被放置在容器的中心。div.logo
  • 根据变量具有背景--highlight-primary
  • 它的左上角和右下角都做了圆角处理,使其形状与羽毛相符。
.logo {
  display: grid;
  place-content: center;

  padding: 0rem 1rem;

  color: var(--highlight-primary);
}

.logo > i {
  border-top-left-radius: 50%;
  border-bottom-right-radius: 50%;
  padding: 0.25rem;

  background: var(--highlight-primary);
  color: var(--bg-color);
}
Enter fullscreen mode Exit fullscreen mode

导航栏本身

让我们在导航栏中添加一些项目。这意味着我们将使用 HTML5 上下文<nav>标签作为导航栏的容器。在导航栏内部,我们使用无序列表,并根据需要进行视觉上的调整。每个导航项的基本结构都是一个包含图标和标签的链接。

<header>
  ...
  <nav>
    <ul>
      <li>
        <a href="#"><i class="far fa-chart-bar"></i>Dashboard</a>
      </li>
      <li>
        <a href="#"><i class="far fa-edit"></i>Projects</a>
      </li>
      <li>
        <a href="#"><i class="far fa-envelope-open"></i>Posts</a>
      </li>
    </ul>
  </nav>
</header>
Enter fullscreen mode Exit fullscreen mode

为了使所有导航项并排排列,无序列表ul也像我们在头部标签中所做的那样,被设置为网格容器。两者之间有两个细微差别:

  1. grid-template-rows属性设置为1fr,表示只有一行,并且这一行应该占用所有可用空间。
  2. gap每个单元格之间都有一个0.5rem
header ul {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  grid-template-rows: 1fr;
  gap: 0.5rem;

  padding: 0rem 1.5rem;

  list-style-type: none;
}
Enter fullscreen mode Exit fullscreen mode

列表li项再次被设置为网格容器,默认情况下,唯一的子元素(<a>)会很好地拉伸到项目的尺寸。

header ul > li {
  display: grid;

  padding: 0.5rem;
}
Enter fullscreen mode Exit fullscreen mode

链接样式

由于我们希望每个链接的内容垂直居中,因此它是一个flex容器,并将align-items样式设置为center。其余样式设置如下:

  • 加一些衬垫,并将边角略微打磨圆润。
  • 设置文本和背景颜色
  • 移除链接中不必要的下划线样式
  • 使背景颜色可过渡,这对于悬停样式非常有用。

每个链接中的图标字体略微缩小,以便与实际文本大小保持良好的比例。

header a {
  display: flex;
  align-items: center;

  padding: 0rem 1.5rem;
  border-radius: 0.25rem;

  color: var(--fg-color);
  background-color: rgba(0, 0, 0, 0.1);

  text-decoration: none;

  transition: background-color var(--transition);
}

header a > i {
  margin-right: 0.5rem;

  color: var(--highlight-primary);

  font-size: 1rem;

  transition: color var(--transition);
}
Enter fullscreen mode Exit fullscreen mode

因此,在悬停样式中,链接的背景颜色更改为高亮颜色,图标的颜色也更改为较深的背景色:

header a:hover {
  background-color: var(--highlight-primary);
}

header a:hover > i {
  color: var(--bg-color);
}
Enter fullscreen mode Exit fullscreen mode

汉堡菜单按钮

当屏幕太小无法容纳所有导航项时,菜单项将被放置在右上角的单独导航栏中。然而,届时我们需要某种触发器或按钮,以便在再次点击时显示或隐藏菜单。因此,我们将使用复选框,因为我们可以通过:checked伪类来响应其状态。由于复选框的样式设置可能非常繁琐,我们将使用一个<label>通过属性引用复选框的标签for。这允许我们将所有需要的样式都应用到标签上,并且由于该for属性包含与复选框相同的 ID,因此点击标签也会切换复选框本身的状态。这样,我们就可以安全地隐藏复选框而不会损失功能(此处暂不讨论可访问性——我将在另一篇文章中讨论)。

<header>
  <div class="logo">...</div>

  <input type="checkbox" class="toggle" id="nav-toggle">
  <label for="nav-toggle" id="nav-toggle-label">
    ...
  </label>

  <nav>...</nav>
</header>
Enter fullscreen mode Exit fullscreen mode

因此,如上所述,标签复选框默认情况下是隐藏的,因为我们默认导航项有足够的空间。

#nav-toggle-label {
  display: none;

  cursor: pointer;
}

#nav-toggle {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode

通过使用@media查询,我们现在可以确定视口的状态,我们将其定义为太小而无法显示导航项。在我们的例子中,如果屏幕/视口尺寸小于768px宽度,则会应用某些样式来改变导航栏的外观和行为。

@media screen and (max-width: 768px) {
  /* 
    styles which are applied only if the page is
    rendered on a screen (e.g. not printed) and
    the viewport's width is less thant 768px wide
  */
}
Enter fullscreen mode Exit fullscreen mode

我们首先要做的是将该nav元素定位得类似于标题fixed,但不要将其固定在视口的顶部边缘,而是固定在右侧:

@media screen and (max-width: 768px) {
  header nav {
    position: fixed;
    top: 0;
    bottom: 0;
    width: 24rem;
    right: 0rem;

    padding-top: var(--nav-height);

    background: var(--gradient);
    box-shadow: -2px 2px 8px 0px rgb(0 0 0 / 80%);

    transition: clip-path var(--transition-long), 
      background-color var(--transition-long);

  }
}
Enter fullscreen mode Exit fullscreen mode

ul现在我们还需要通过grid-auto-flowcolumn到 来改变无序列表内部网格的流向row。这样,导航项就会垂直排列。

@media screen and (max-width: 768px) {
header ul {
    grid-auto-flow: row;
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-rows: max-content;
    gap: 0.5rem;

    padding: 0;
  }
}
Enter fullscreen mode Exit fullscreen mode

其余样式稍微改变了链接的对齐方式(左对齐place-content),并改变了默认状态和悬停状态下的颜色。

@media screen and (max-width: 768px) {

  header a {
    place-content: flex-start;

    padding: 0.5rem 1.5rem;
  }

  header a > i {
    color: var(--bg-color);
  }

  header a:hover {
    background-color: var(--bg-color);
  }

  header a:hover > i {
    color: var(--highlight-primary);
  }
}
Enter fullscreen mode Exit fullscreen mode

菜单按钮

最后,是时候设置菜单按钮的样式了。首先,我们来配置header标签,使徽标保持在左侧,菜单按钮移到右侧,这可以通过设置justify-content以下值来实现space-between

@media screen and (max-width: 768px) {
  header {
    justify-content: space-between;
    align-items: center;
  }
}
Enter fullscreen mode Exit fullscreen mode

要创建一个菜单按钮,使其能够从带有菜单图标(三个堆叠在一起的横条)的按钮过渡到带有关闭图标的按钮,我们只需div.bars在 中放入三个label

<header>
  ...
  <label for="nav-toggle" id="nav-toggle-label">
    <div class="bar"></div>
    <div class="bar"></div>
    <div class="bar"></div>
  </label>
  ...
</header>
Enter fullscreen mode Exit fullscreen mode

那么,我们先来做一些准备工作:

  • --size变量控制菜单按钮的大小,并与导航栏的高度成正比。
  • --bar-height变量告诉每个柱状图它应该有多高。
  • column这些条形图是通过使用 flex-box 沿方向堆叠排列的,space-between使得所有条形图均匀分布在包含元素的高度上。
@media screen and (max-width: 768px) {
  #nav-toggle-label {
    --size: calc(var(--nav-height) / 3);
    --bar-height: 2px;

    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex-basis: auto;

    width: var(--size);
    height: var(--size);
    margin-right: calc(var(--nav-height) / 3);

    z-index: 2;
  }
Enter fullscreen mode Exit fullscreen mode

每个柱状图本身都只是简单地设置了--bg-color背景颜色,并使用了--bar-height上面提到的变量。然后width: 100%;,它会被拉伸以占据所有可用的水平空间。

  #nav-toggle-label .bar {
    display: inline-block;

    height: var(--bar-height);
    width: 100%;

    background-color: var(--bg-color);

    transition: transform 250ms ease-out;
  }
}
Enter fullscreen mode Exit fullscreen mode

现在有趣的部分来了,我们要定义如何将三个横条重新排列成一个关闭图标。每个选择器都绑定到:checked复选框的伪类,因此如果复选框未被选中,则这些样式不会应用。

  • 最上面的横条旋转了 225 度,并向下移动到包含元素的中心。
  • 最下面的横杆旋转了 135 度(225 度减去 90 度),并向上移动到包含元素的中心。
  • 只需将中间元素沿 x 轴缩放至零,即可使其不可见。
@media screen and (max-width: 768px) {
  #nav-toggle:checked + #nav-toggle-label > .bar:nth-child(1) {
    transform: 
      translate(0, calc(var(--size) / 2 - var(--bar-height) / 2)) 
      rotate(225deg);
  }

  #nav-toggle:checked + #nav-toggle-label > .bar:nth-child(2) {
    transform: scaleX(0);
  }

  #nav-toggle:checked + #nav-toggle-label > .bar:nth-child(3) {
    transform: 
      translate(0, calc(-1 * var(--size) / 2 + var(--bar-height) / 2)) 
      rotate(135deg);
  }
}
Enter fullscreen mode Exit fullscreen mode

展开和折叠导航菜单

我们快完成了!现在我们需要将导航栏简化成一个小圆圈,如果复选框未选中,则将其放置在菜单图标的正后方。因此,我们使用一个clip-path半径为1rem导航栏高度三分之一的圆,圆心位于视口的右上角,但略微向左下方偏移,以匹配导航栏的高度。

@media screen and (max-width: 768px) {
  header nav {
    ...

    clip-path: circle(
        calc(var(--nav-height) / 3)
        at 
        calc(100% - var(--nav-height) / 2) 
        calc(0% + var(--nav-height) / 2)
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

然后,将这个圆扩展到视口较大边缘的 125%(125vmax)。这个vmax单位非常方便,因为它会动态地将基准尺寸更改为视口的宽度或高度——具体取决于两者中哪个更大。因此,较大边缘的 125% 足以覆盖视口的高度。

@media screen and (max-width: 768px) {
  #nav-toggle:checked + * + nav {
    clip-path: circle(125vmax at 100% 0%);

    background-color: var(--bg-color);
  }
}
Enter fullscreen mode Exit fullscreen mode

最后,我们还对该clip-path属性进行了平滑过渡处理,使菜单的展开和折叠更加流畅。就这样,就完成了!

@media screen and (max-width: 768px) {
  header nav {
    ...    
    transition: clip-path var(--transition-long), 
      background-color var(--transition-long);

  }
}
Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/crayoncode/let-s-build-a-responsive-navbar-from-scratch-1923