前几天,我通过手机访问了 Netflix Jobs 网站,并查看了它的画布菜单。我真的很喜欢每次击中它时都会触发的惊人动画。所以我认为以这个菜单为灵感,向你展示如何构建一个类似的响应式菜单可能是一个很好的练习。
我们正在构建什么
在开始之前,我希望你们都清楚地了解我们将要构建的内容:
See the Pen Build an Advanced Responsive Menu Inspired by Netflix by Envato Tuts+ (@tutsplus) on CodePen.
这个嵌入式笔显示我们菜单的移动版本。请务必在更宽的屏幕上查看它以查看它的桌面版本。随意调整浏览器窗口的大小,以查看布局如何适应各种屏幕尺寸。
我们有很多东西要讲,所以让我们开始吧!
资产
出于本教程的目的,我已将以下资产合并到笔中:
Roboto Google字体
字体 真棒库
此处将使用的 Forecastr 徽标取自 Envato Elements。
1. 从页面标记开始
页面标记乍一看可能看起来很长,但请不要感到不知所措。事实上,它并不像看起来那么复杂。无论如何,我会尽力解释它!
看看下面:
<header class="page-header">
<nav>
<button aria-label="Open Mobile Menu" class="open-mobile-menu fa-lg">
<i class="fas fa-bars" aria-hidden="true"></i>
</button>
<a href="">
<img class="logo horizontal-logo" src="horizontal-logo.svg" alt="">
<img class="logo vertical-logo" src="vertical-logo.svg" alt="">
</a>
<div class="top-menu-wrapper">
<div class="panel panel1"></div>
<div class="panel panel2"></div>
<ul class="top-menu">
<li class="mob-block">
<img class="logo" src="horizontal-logo-mobile.svg" alt="">
<button aria-label="Close Mobile Menu" class="close-mobile-menu fa-lg">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</li>
<li>...</li>
<li>...</li>
<li class="has-dropdown">
...
<ul class="sub-menu">...</ul>
</li>
<li class="has-dropdown">
...
<ul class="sub-menu">...</ul>
</li>
<li>
<ul class="socials">...</ul>
</li>
</ul>
<button class="search">...</button>
<form class="search-form">
<div>
<input type="search" placeholder="Search Resources">
<button aria-label="Search Resources" type="submit">
<i class="fas fa-search fa-2x" aria-hidden="true"></i>
</button>
</div>
</form>
</div>
</nav>
</header>
让我揭开这里发生的一切的神秘面纱。
我们将从 header 包含nav (navbar) 的 a 开始。在其中,我们将放置所有标题元素。进一步来说:
将打开画布外菜单的汉堡包按钮。这仅在中小型屏幕 (<995px) 上可见。
徽标。我们将有两种不同类型的徽标。一个垂直标志和一个水平标志。它们的可见性将取决于视口大小。
.top-menu-wrapper 元素。 这将包括两个空.panel元素, .top-menu 列表、搜索按钮和搜索表单。s 仅在.panel中小型屏幕 (<995px) 上可见。在.top-menu列表中,我们将放置 .mob-block 将包装一些仅限移动设备的元素、菜单链接和社交链接的元素。.panel与s 类似,the.mob-block 和社交链接只会出现在中小屏幕(<995px)上。
2. 定义一些基本样式
准备好标记后,我们将继续使用 css。我们的第一步是设置一些 CSS 变量和常见的重置样式:
:root {
--purple-1: #3d174f;
--purple-2: #4b2860;
--white: #fff;
--black: #221f1f;
--red: #ed1849;
--lightgray: #cfcfcf;
--overlay: rgba(0, 0, 0, 0.5);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
html {
font-size: 62.5%;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
ul {
list-style: none;
}
a {
text-decoration: none;
}
img {
display: block;
max-width: 100%;
height: auto;
}
a,
button {
color: inherit;
}
.no-transition {
transition: none !important;
}
body {
font: 1.6rem/1.5 Roboto, sans-serif;
color: var(--white);
min-height: 100vh;
}
这里没有什么壮观的。我只想讨论两件事。
首先,请注意我们给 font-size: 62.5%了html. 这会将基本字体大小设置为 10px ((62.5/100)*16) 并覆盖默认的浏览器字体大小,即 16px(尽管它是用户可配置的)。通过这样做,1rem 等于 10px 而不是 16px。这使我们可以轻松地为我们的元素指定基于 rem 的大小。
其次,重视no-transition课堂。稍后我们将使用该类在调整页面大小时禁用所有 CSS 过渡。
注意:为简单起见,我不会 在教程中介绍所有CSS 规则。这里有将近 400 行 CSS。 确保通过单击演示项目的CSS选项卡来检查它们 。
3. 为移动菜单设置样式
为了设置导航栏的样式,我们将遵循移动优先的方法。也就是说,首先我们将在中小型屏幕 (<995px) 上浏览它的布局,然后在大屏幕上进行。
考虑到这一点,当视口大小低于 995 像素时,导航栏将如下所示:
如您所见,此时只会出现汉堡包按钮、垂直徽标和搜索按钮(不包括其文本)。
导航栏将充当弹性容器。我们将给它 justify-content: space-between并 align-items: center 相应地将其可见的子代定位在主轴和交叉轴上。
以下是对应的样式:
/*CUSTOM VARIABLES HERE*/
.page-header {
padding: 1.5rem 3rem;
background: var(--purple-1);
}
.page-header nav {
display: flex;
align-items: center;
justify-content: space-between;
}
.page-header .horizontal-logo,
.page-header .search span {
display: none;
}
打开画布外
每次我们点击汉堡菜单时,.top-menu-wrapper 元素都会接收到show-offcanvas 类。在这种情况下,将出现画布外菜单:
这将带有过渡效果。.panel 和元素将.top-menu根据它们的源顺序以滑入效果顺序可见。首先.panel1会出现,然后在 200 毫秒后出现,.panel2最后在 400 毫秒后出现.top-menu。同时,我们将为::before. .top-menu-wrapper此伪元素将用作位于画布外菜单下方的叠加层。
在这一点上,重要的是写下有关画布外布局的一些关键内容:
.panels 和 the都.top-menu将是固定定位的元素并覆盖整个视口高度。它们还将位于所有其他元素之上。
它们的宽度将取决于视口大小。例如,在最大 549px 的屏幕上,它们的宽度将覆盖整个视口宽度。另一方面,在较大的屏幕上,它们都有固定的宽度(大约是窗口宽度的 60%)。
它们出现的顺序将取决于其transition-delay财产的价值。
我们将使用 flexbox 来布局.top-menu 和.mob-block元素的内容。
以下是画布外菜单所需的部分样式:
/*CUSTOM VARIABLES HERE*/
.page-header .top-menu-wrapper::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
transition: background 0.5s;
}
.page-header .panel,
.page-header .top-menu {
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 2;
transform: translate3d(-100%, 0, 0);
transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1);
}
.page-header .panel1 {
width: 100%;
background: var(--purple-1);
}
.page-header .panel2 {
width: calc(100% - 3rem);
background: var(--red);
}
.page-header .top-menu {
display: flex;
flex-direction: column;
width: calc(100% - 6rem);
overflow-y: auto;
padding: 2rem;
background: var(--white);
}
.page-header .top-menu .mob-block {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 3rem;
}
.page-header .top-menu-wrapper.show-offcanvas::before {
background: var(--overlay);
z-index: 1;
}
.page-header .top-menu-wrapper.show-offcanvas .panel,
.page-header .top-menu-wrapper.show-offcanvas .top-menu {
transform: translate3d(0, 0, 0);
transition-duration: 0.7s;
}
.page-header .top-menu-wrapper.show-offcanvas .panel1 {
transition-delay: 0s;
}
.page-header .top-menu-wrapper.show-offcanvas .panel2 {
transition-delay: 0.2s;
}
.page-header .top-menu-wrapper.show-offcanvas .top-menu {
transition-delay: 0.4s;
box-shadow: rgba(0, 0, 0, 0.25) 0 0 4rem 0.5rem;
}
@media screen and (min-width: 550px) {
.page-header .panel1 {
width: 60%;
}
.page-header .panel2 {
width: calc(60% - 3rem);
}
.page-header .top-menu {
width: calc(60% - 6rem);
}
}
以及打开它所需的 javascript 代码:
const openMobMenu = document.queryselector(".open-mobile-menu");
const topMenuWrapper = document.querySelector(".top-menu-wrapper");
const showOffCanvas = "show-offcanvas";
openMobMenu.addeventListener("click", () => {
topMenuWrapper.classList.add(showOffCanvas);
});
关闭画布外
每次我们点击 ✕ 按钮,.top-menu-wrapper 都会失去它的 show-offcanvas类别。
就在这个时候, .panel 和 .top-menu 元素会以相反的顺序以滑出的效果消失。首先 .top-menu 将消失,然后在 100 毫秒后消失, .panel2最后在 300 毫秒 后消失.panel1。
以下是与过渡相关的样式,它们决定了目标元素的速度:
.page-header .panel,
.page-header .top-menu {
transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1);
}
.page-header .panel1 {
transition-delay: 0.3s;
}
.page-header .panel2 {
transition-delay: 0.1s;
}
以及隐藏画布外的 JavaScript 代码:
const closeMobMenu = document.querySelector(".close-mobile-menu");
const topMenuWrapper = document.querySelector(".top-menu-wrapper");
const showOffCanvas = "show-offcanvas";
closeMobMenu.addEventListener("click", () => {
topMenuWrapper.classList.remove(showOffCanvas);
});
切换表格
无论视口宽度如何,搜索表单最初都会被隐藏。它也将位于导航栏的正下方。
相关样式:
/*CUSTOM VARIABLES HERE*/
.page-header {
position: relative;
}
.page-header .search-form {
position: absolute;
top: 100%;
left: 0;
right: 0;
visibility: hidden;
opacity: 0;
padding: 1rem 0;
background: var(--purple-2);
transition: all 0.2s;
}
.page-header .search-form.is-visible {
visibility: visible;
opacity: 1;
}
只要我们点击搜索按钮,表单的可见性状态就会发生变化。这意味着如果它被隐藏,它将出现(它将接收is-visible类)。但是如果它已经有这个类,它就会消失。
处理表单可见性的 javaScript 代码:
const toggleSearchForm = document.querySelector(".search");
const searchForm = document.querySelector(".page-header form");
const isVisible = "is-visible";
toggleSearchForm.addEventListener("click", () => {
searchForm.classList.toggle(isVisible);
});
4. 设置桌面菜单样式
当视口宽度至少为 995px 时,导航栏布局将完全不同:
在这种情况下,典型的导航菜单将取代临时的画布外菜单。
因此,让我们强调一下桌面布局与移动版布局的重要区别:
将出现水平徽标。
另一方面,以下元素将消失: 的::before伪元素.top-menu-wrapper、汉堡切换按钮、垂直徽标、.panels、the.mob-block和社交链接。
此外, .top-menu将不再作为固定定位元素,而是根据其正常的文档流 ( position: static) 定位。另外,它的下拉菜单默认是隐藏的,只有当我们将鼠标悬停在其相应的父列表项上时才会出现。
这是桌面布局最关键的样式的一部分:
/*CUSTOM VARIABLES HERE*/
@media screen and (min-width: 995px) {
.page-header .panel,
.page-header .open-mobile-menu,
.page-header .vertical-logo,
.page-header .top-menu .mob-block,
.page-header .top-menu > li:last-child,
.page-header .top-menu-wrapper::before {
display: none;
}
.page-header .horizontal-logo {
display: block;
}
.page-header .top-menu-wrapper {
display: flex;
align-items: center;
color: var(--white);
}
.page-header .top-menu {
flex-direction: row;
position: static;
width: auto;
background: transparent;
transform: none;
padding: 0;
overflow-y: visible;
box-shadow: none !important;
}
.page-header .has-dropdown i {
display: inline-block;
}
.page-header .sub-menu {
display: none;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
padding: 1.5rem 2rem;
background: var(--purple-2);
}
.page-header .has-dropdown {
position: relative;
}
.page-header .has-dropdown:hover .sub-menu {
display: block;
}
}
当视口宽度至少为 1200 像素时,我们将做一些小的更改。最重要的是,我们将显示搜索按钮的文本(搜索资源):
显示搜索文本的样式:
@media screen and (min-width: 1200px) {
.page-header .search span {
display: block;
}
}
5. 清除窗口调整大小的过渡
我们几乎完成了菜单的创建。但是我们必须解决最后一件事。更具体地说,每次我们调整浏览器窗口的大小时,我们都应该清除所有的 CSS 过渡。否则,在我们的例子中,当我们调整窗口大小时,画布外菜单将出现片刻,然后返回到其默认的屏幕外位置。
虽然这不是很重要的事情,但如果我们能找到解决这个问题的方法,那就太好了。
所以我们可以做的是监听resize事件,每次触发时,我们都会将no-transition 类(希望你记得它!)添加到所有.page-header 后代。
但是,当调整大小完成后,我们将等待 500 毫秒(随意更改该值),然后从所有 .page-header 后代中删除该类。
下面是实现此功能的一小段 JavaScript 代码:
const pageHeader = document.querySelector(".page-header");
const noTransition = "no-transition";
let resize;
window.addEventListener("resize", () => {
pageHeader.querySelectorAll("*").forEach(function(el) {
el.classList.add(noTransition);
});
clearTimeout(resize);
resize = setTimeout(resizingComplete, 500);
});
function resizingComplete() {
pageHeader.querySelectorAll("*").forEach(function(el) {
el.classList.remove(noTransition);
});
}
我不确定这是否是解决此问题的最聪明的方法,但我认为至少它可以正常工作并取消窗口调整大小时不需要的过渡。
显然,我们可以增强此代码,使其仅对极少数被转换的元素运行,而不是对所有标题的后代运行。
结论
就是这样的人!我们以 Netflix Jobs 网站的标题菜单为灵感,并学会了创建我们自己的高级响应式菜单。确实这是一个漫长的旅程,但我希望它可以帮助你提高你的前端技能并学习一些新东西。
让我们再看看我们构建了什么:
See the Pen Build an Advanced Responsive Menu Inspired by Netflix by Envato Tuts+ (@tutsplus) on CodePen.
在结束之前,我想再说一件事:了解这个演示如何工作的最好方法是检查 CSS 样式。
如果您打算构建类似的东西,我很想知道您的想法!一如既往,非常感谢您的阅读!
- 我们正在构建什么
- 资产
- 打开画布外
- 关闭画布外
- 切换表格