# 兼容多终端响应式项目开发,标准规范 和 最佳实践
TIP
从本节开始,会带领 从 0 到 1,亲历 多终端企业真实项目开发,综合提升 CSS 布局 及 企业开发标准 和 规范 及 最佳实践。
PS 还原 UI、蓝湖标注切图 -> CSS 方法论、大厂 PC 端项目开发标准和规范 -> SEO 优化实践,手写代码 + Git 代码管理 + 云服务器项目部署 与 发布
项目预览地址:https://www.arryblog.com/web/project/responsive/show.html (opens new window)
# 一、项目开发流程
TIP
以下是本次项目的开发流程(针对企业真实的大型项目开发,接到项目后,就要开会一起讨论,定项目的功能设计和开发流程、进度等)
# 1、第一步:我们需要分析整个项目有哪些具体的功能
TIP
- 响应式功能 、主题皮肤切换功能
- 大量 JavaScript 特效
- 页面加载进度条、滑动二级下拉菜单
- 垂直二级伸缩菜单、响应式二级菜单、抽屉式菜单
- 吸顶盒菜单、右侧悬浮菜单
- 数字动画、带左右按扭的轮播图、Tab 选项卡切换特效
- 滑块跟随鼠标导航、导航吸顶盒效果
- 楼梯式导航特效、点击弹出显示大图轮播
- 页面滚动加载动画、点击返回页面顶部
- 大量 CSS3 动画
- 淡入淡出动画、鼠标悬停动画
- 3D 翻转动画、滑动扫光效果、复杂的过渡动画
# 2、第二步:针对项目的功能,选择采用什么样的实现方案
TIP
- 针对响应式功能,我选择自己手写响应式栅格系统来实现 (手写
media.css
) - 针对主题皮肤的切换功能,我选择采用 原生 JS + CSS 手动实现
- 针对项目中的 JS 动画特效,播图我们选用 Swiper 插件来实现,其余动画均采用手写原生 JS 实现
- CSS3 动画能用
animate.css
库实现的都用他实现,其它的均自己手写。
# 3、第三步:开始项目架构的搭建
TIP
由于受限于现阶段所学知识,该项目只是静态页面开发 + JavaScript 效果,暂时不涉及 前端框架相关内容,整个项目架构的搭建同时整合了一线互联网大厂项目最佳实践 和 规范。
为我们先一步建立企业项目开发思维流程 和 认知。
# 二、项目开发架构搭建
TIP
在正式进入项目具体功能开发前,我们需要先搭建好项目架构。本项目架构搭建,主要完成以下内容
- 创建项目的目录结构
- 重置默认样式
- 设置项目通用字体样式
- 定制主题皮肤
- 准备项目所需要的图片素材
- 实现响应式栅格系统
- 准备好项目中用到 iconfont 图标
- 使用 git 管理项目
# 1、创建项目的目录结构
TIP
- 新建项目文件夹
response-web
- 在
response-web
中新建css
、js
、images
文件夹,分别用来存放 CSS、JS 文件和图片 - 在
css
文件夹中新建reset.css
、global.css
、index.css
文件,分别用来存放重置默认样式、全局通用样式,首页样式
response-web // 项目文件夹 - 用来存文本项目所有内容
├─ css // css文件夹,用来存放 css 文件
│ ├─ global.css // 全局通用样式
│ ├─ index.css // 项目首页样式
│ └─ reset.css // 重置默认样式
├─ images // images 文件夹,用来存放项目图片素材
├─ index.html // 项目首页
└─ js // js 文件夹,用来存放 js 文件
# 2、重置默认样式
TIP
在 css/reset.css
文件中,添加如下 css,用来重置 html 标签的默认样式
点击查看完整源代码
@charset "utf-8";
/* --------------------重置样式-------------------------------- */
body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
button,
input,
textarea,
th,
td {
margin: 0;
padding: 0;
}
/* 设置默认字体 */
body {
font-size: 14px;
font-style: normal;
font-family: -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica
neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe
ui symbol, noto color emoji;
}
/* 字体太小用户体检不好,让small恢复12px */
small {
font-size: 12px;
}
h1 {
font-size: 18px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 14px;
}
h4,
h5,
h6 {
font-size: 100%;
}
ul,
ol {
list-style: none;
}
a {
text-decoration: none;
background-color: transparent;
}
a:hover,
a:active {
outline-width: 0;
text-decoration: none;
}
/* 重置表格 */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 重置hr */
hr {
border: 0;
height: 1px;
}
/* 图形图片 */
img {
border-style: none;
}
img:not([src]) {
display: none;
}
svg:not(:root) {
overflow: hidden;
}
/* 下面的操作是针对于html5页面布局准备的,不支持ie6~8以及其他低版本的浏览器 */
html {
/* 禁用系统默认菜单 */
-webkit-touch-callout: none;
/* 关闭iphone & Android的浏览器纵向和横向模式中自动调整字体大小的功能 */
-webkit-text-size-adjust: 100%;
}
input,
textarea,
button,
a {
/* 表单或者a标签在手机点击时会出现边框或彩色的背景区域,意思是去除点击背景框 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* 重置html5元素的默认样式 */
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
}
audio:not([controls]),
video:not([controls]) {
display: none;
height: 0;
}
progress {
vertical-align: baseline;
}
mark {
background-color: #ff0;
color: #000;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
button,
input,
select,
textarea {
font-size: 100%;
outline: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
textarea {
overflow: auto;
}
button,
html [type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
关于标签默认样式及清除相关内容,可以参考博客 标签默认博客地 (opens new window) (opens new window) (opens new window)👆 中如下截图内容
# 3、设置通用字体样式
TIP
通过对设计稿的分析,我们可以了解页面字体通用样式
- 页面中大部分字体为
16px
- 行间距为字体大小的
1.5 倍
(每个公司要求不一样,在实际开发中,以公司规范定为主) - 字体类型主要是微软雅黑
- 字体颜色主要以
#000
黑色为主
通过以上数据最后得到如下代码,添加到
./css/global.css
文件中,作为全局通用样式
/* 项目通用字体样式 */
body {
/* 字体大小: 16px
行高:为字体的 1.5倍
字体类型:微软雅黑 */
font: 16px/1.5 Microsoft YaHei;
color: #000; /* 字体颜色 */
}
# 4、定制主题皮肤
TIP
通过对设计稿的用色分析,可以定制出网站的皮肤主题。
以下是本项目三种不同皮肤主题的颜色(这部分主要由设计师来定)
主题二和主题三,在这个项目中,只是主色调最深的色不一样,其它都一样
注:
通过给出的三套皮肤色,我们可以定义三套皮肤主题。
在css
文件夹中,新建 skin-theme.css
文件,用来保存当前网站的皮肤主题
通过更新 html 标签上自定义属性
data-theme
的值来切换到不同主题
/* 清绿色 turquoise 默认主题 */
:root,
html[data-theme="turquoise"] {
/* 主色调 */
--primary--color: #1ec28b; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
/* 深黄色 #ffd336 */
html[data-theme="yellow"] {
/* 主色调 */
--primary--color: #ffd336; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
/* 深红色 */
html[data-theme="red"] {
/* 主色调 */
--primary--color: #ff383f; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
# 4.1、使用皮肤主题
TIP
- 在
index.html
页面中引用skin-theme.css
文件 - 然后,使用
skin-theme.css
文件中定义的主题皮肤色
<link rel="stylesheet" href="./css/skin-theme.css" />
<style>
.box {
width: 200px;
height: 100px;
/* 背景颜色使用css变量,在不同的皮肤主题下,背景颜色不一样 */
background-color: var(--primary-color);
}
</style>
</head>
<body>
<div class="box">我是一个长方形,不同皮肤主题下,我的背景颜色不一样。</div>
</body>
# 4.2、皮肤主题切换原理
TIP
通过更新 html 标签上自定义属性 data-theme
的值来切换到不同主题
<!-- 将 data-theme 的值更新为 red 、 yellow 、 turquoise 时,可以看到不同 -->
<html lang="en" data-theme="red">
<!-- --->
</html>
data-theme
属性不同值的效果
data-theme="red" | data-theme="yellow" | data-theme="turquoise" |
---|---|---|
# 5、准备项目开发所需的图片素材
TIP
把项目的 psd 设计稿标注好切图,然后上传到蓝湖 (在公司,一般都有设计师来完成)
项目 psd 设计稿已上传到钉钉群文件,自行下载。关于如何用 ps 标注切图,讲面讲过了。
如果不会,可以然钉钉群看如下视频
打开蓝湖,找到项目设计稿,导出项目的所有切图(正常情况下,设计师会把标注好的图片取上合理的名字,但如果没有,你就需要对图重命名)
将导出来的图片素材,复制到项目的
images
文件夹下
# 6、实现响应式栅格系统
TIP
根据以下 3 步,来确定响应式栅格系统的具体代码
- 将页面分成 12 份,确定栅格布局不同份数所点比例
- 确定项目对应的断点,这里我们采用标准的断点
屏幕大小 | 栅格布局中 class 名区分 | 断点(阈值) |
---|---|---|
超小屏(Extra small ) | <576px | |
小屏 (Small) | -sm | 576px ~ 768px (含等于) |
中屏 (Medium) | -md | 768px ~ 992px (含等于) |
大屏 (Large) | -lg | 992px ~ 1200px(含等于) |
超大屏 (X-Large) | -xl | 1200px ~ 1400px(含等于) |
超大大屏(XX-Large) | -xxl | >1400px |
- 确定适配方案,以 PC 端优先
- 按前面三步,实现响应式栅格系统(也就是书写
media.css
样式)
点击查看完整源代码
/*
第一:我们将页面分成12分
第二:我们选择的断点是行业标准断点
第三:我们选择的适配方案,是PC端优先
*/
/* ....这里的css样式,会在屏幕宽大于1400px时生效.... */
.col-xxl-1 {
width: 8.333333%;
}
.col-xxl-2 {
width: 16.6666667%;
}
.col-xxl-3 {
width: 25%;
}
.col-xxl-4 {
width: 33.33333333%;
}
.col-xxl-5 {
width: 41.66666667%;
}
.col-xxl-6 {
width: 50%;
}
.col-xxl-7 {
width: 58.33333333%;
}
.col-xxl-8 {
width: 66.6666667%;
}
.col-xxl-9 {
width: 75%;
}
.col-xxl-10 {
width: 83.33333333%;
}
.col-xxl-11 {
width: 91.66666667%;
}
.col-xxl-12 {
width: 100%;
}
/* 当屏幕宽度大于1200px ,但小于等于1400px时,显示如下样式 */
@media screen and (max-width: 1400px) {
.col-xl-1 {
width: 8.333333%;
}
.col-xl-2 {
width: 16.6666667%;
}
.col-xl-3 {
width: 25%;
}
.col-xl-4 {
width: 33.33333333%;
}
.col-xl-5 {
width: 41.66666667%;
}
.col-xl-6 {
width: 50%;
}
.col-xl-7 {
width: 58.33333333%;
}
.col-xl-8 {
width: 66.6666667%;
}
.col-xl-9 {
width: 75%;
}
.col-xl-10 {
width: 83.33333333%;
}
.col-xl-11 {
width: 91.66666667%;
}
.col-xl-12 {
width: 100%;
}
}
/* 当屏幕宽度大于992px ,但小于等于1200px时,显示如下样式 */
@media screen and (max-width: 1200px) {
.col-lg-1 {
width: 8.333333%;
}
.col-lg-2 {
width: 16.6666667%;
}
.col-lg-3 {
width: 25%;
}
.col-lg-4 {
width: 33.33333333%;
}
.col-lg-5 {
width: 41.66666667%;
}
.col-lg-6 {
width: 50%;
}
.col-lg-7 {
width: 58.33333333%;
}
.col-lg-8 {
width: 66.6666667%;
}
.col-lg-9 {
width: 75%;
}
.col-lg-10 {
width: 83.33333333%;
}
.col-lg-11 {
width: 91.66666667%;
}
.col-lg-12 {
width: 100%;
}
}
/* 当屏幕宽度大于768px ,但小于等于992px时,显示如下样式 */
@media screen and (max-width: 992px) {
.col-md-1 {
width: 8.333333%;
}
.col-md-2 {
width: 16.6666667%;
}
.col-md-3 {
width: 25%;
}
.col-md-4 {
width: 33.33333333%;
}
.col-md-5 {
width: 41.66666667%;
}
.col-md-6 {
width: 50%;
}
.col-md-7 {
width: 58.33333333%;
}
.col-md-8 {
width: 66.6666667%;
}
.col-md-9 {
width: 75%;
}
.col-md-10 {
width: 83.33333333%;
}
.col-md-11 {
width: 91.66666667%;
}
.col-md-12 {
width: 100%;
}
}
/* 当屏幕宽度大于576px ,但小于等于768px时,显示如下样式 */
@media screen and (max-width: 768px) {
.col-sm-1 {
width: 8.333333%;
}
.col-sm-2 {
width: 16.6666667%;
}
.col-sm-3 {
width: 25%;
}
.col-sm-4 {
width: 33.33333333%;
}
.col-sm-5 {
width: 41.66666667%;
}
.col-sm-6 {
width: 50%;
}
.col-sm-7 {
width: 58.33333333%;
}
.col-sm-8 {
width: 66.6666667%;
}
.col-sm-9 {
width: 75%;
}
.col-sm-10 {
width: 83.33333333%;
}
.col-sm-11 {
width: 91.66666667%;
}
.col-sm-12 {
width: 100%;
}
}
/* 当屏幕宽度小于等于576px时,显示如下样式 */
@media screen and (max-width: 576px) {
.col-1 {
width: 8.333333%;
}
.col-2 {
width: 16.6666667%;
}
.col-3 {
width: 25%;
}
.col-4 {
width: 33.33333333%;
}
.col-5 {
width: 41.66666667%;
}
.col-6 {
width: 50%;
}
.col-7 {
width: 58.33333333%;
}
.col-8 {
width: 66.6666667%;
}
.col-9 {
width: 75%;
}
.col-10 {
width: 83.33333333%;
}
.col-11 {
width: 91.66666667%;
}
.col-12 {
width: 100%;
}
}
# 7、下载项目中用到的 iconfont 图标
TIP
进入网站 iconfont 阿里图标库 (opens new window), 把下面图标加入到购物车,然后添加到项目,最后将所有图标下载下来。
将存放图标对应文件夹名改为
iconfont
,放在当前项目根目录下。
温馨提示:
如果不会下载 iconfont 图标,可以进入 iconfont 阿里矢量图标库 (opens new window) 页面,找到如下图所示位置, 查看详细操作教程
# 8、使用 Git 管理项目
TIP
按以下步骤,使用 Git 来管理项目
- 第一步:安装 Git
- 第二步:Git 的基础配置
- 第三步:初始化 Git 本地仓库
- 第四步:新建
.gitignore
文件 和README.md
文件 - 第五步:使用 Gitee,新建远程仓库
- 第六步:添加远程仓库提交地址
- 第七步:Git 提交,提交代码到本地仓库 和 远程仓库
如果不会操作 Git,可以进入 Git 初始化项目 (opens new window) 查看详细 Git 操作教程。(需要视频版教程,可以联系客服免费获取)
# 三、正式进入项目开发
TIP
首先分析整个设计稿,确定接下来的布局方向。
整个项目由 9 大版组成,各版块之间相互独立,核心内容在 1200px 区域中居中显示。
所以,我们采取从上到下,一个版块一个版块来实现,最后再实现侧边栏相关内容布局(如:楼梯式导航、皮肤主题切换、右侧悬浮菜单)
接下来,在 index.html
页面按顺序引入 css 文件
<!DOCTYPE html>
<html lang="en" data-theme="turquoise">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>兼容多终端响应式网站</title>
<!-- 重置 html 标签默认样式 -->
<link rel="stylesheet" href="./css/reset.css" />
<!-- 引入主题皮肤样式 -->
<link rel="stylesheet" href="./css/skin-theme.css" />
<!-- 引入全局样式 -->
<link rel="stylesheet" href="./css/global.css" />
<!-- 引入首页样式 -->
<link rel="stylesheet" href="./css/index.css" />
<!-- 引入媒体查询 栅格系统样式 -->
<link rel="stylesheet" href="./css/media.css" />
</head>
<body></body>
</html>
# 四、网站头部导航
以下是网站头部导航的具体实现步骤
# 1、第一步:渐变色背景的容器
- 因为导航和轮播图在同一个容器,所以在实现菜单前,先要创建好渐变色背景的容器
- 背景色是一个从上到下的径向渐变,并且颜色有 40% 的透明度。
- 因为我们这里的颜色是直接通过 CSS var 函数引用 主题皮肤中的自定义属性来实现的,所以只能通过以下方式实现背景色透明度
background-image: linear-gradient(
to bottom,
var(--primary--color),
var(--white-color)
);
pacity: 0.4;
- 这里我们采用伪元素来绘制这个渐变的背景,然后定位在当前容器上面。
- 为了让菜单还有轮播图内容能显示在渐变背景上面,所以我们还要创建一个用来放 菜单和轮播图的 div 元素,然后采用定位,定位在渐变的背景色上面。
<style>
.header {
width: 100%;
height: 758px;
position: relative; /* 相对定位 */
}
.header::before,
.header .header-content {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.header::before {
content: "";
background-image: linear-gradient(
to bottom,
var(--primary--color),
var(--white-color)
);
opacity: 0.4;
}
/* .header .header-content {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
} */
</style>
<!-- 网页头部 开始 -->
<header class="header">
<div class="header-content">绝对定位元素 - 有来放置导航和轮播图</div>
</header>
<!-- 网页头部 结束 -->
# 2、第二步:制作顶部放置 logo 和 导航容器
<style>
.top {
height: 80px;
}
/* 这个样式为通用样式,移到 global.css 中,但是这里为了演示,暂时放在这里。 */
.layout {
max-width: 1200px; /* 这里要用 max-width ,这样页面缩小时,容器也会缩小 */
margin: 0 auto;
}
.top .top-content {
height: inherit;
background-color: red; /* 后面会去掉 */
}
</style>
<!-- 网页头部 开始 -->
<header class="header">
<div class="header-content">
<!-- top开始 -->
<div class="top">
<div class="top-content layout"></div>
</div>
<!-- top结束 -->
</div>
</header>
<!-- 网页头部 结束 -->
# 3、第三步:添加 logo 和 导航
TIP
利用 flex 弹性布局实现 logo 和导航水平两端对齐,同时垂直居中对齐
<style>
.top .top-content {
/* 省略了部分css 具体见前面 */
display: flex; /* 开启弹性布局 - 所有子项默认水平排列 */
align-items: center; /* 垂直方向居中对齐 */
justify-content: space-between; /* 水平方向两端对齐 */
}
.top ul.menu {
display: flex; /* 开启弹性布局 - 所有子项默认水平排列 */
}
</style>
<!-- top开始 -->
<div class="top">
<div class="top-content layout">
<!-- logo开始 -->
<div class="logo">
<a href="#">
<img src="./images/logo.png" alt="艾编程logo" width="118" />
</a>
</div>
<!-- logo开始 -->
<!-- 导航开始 -->
<nav>
<ul class="menu">
<li><a href="">网站首页</a></li>
<li><a href="">国内游</a></li>
<li><a href="">出镜游</a></li>
<li><a href="">周边游</a></li>
<li><a href="">主题游</a></li>
<li><a href="">自由行</a></li>
<li><a href="">合作案例</a></li>
<li><a href="">关于我们</a></li>
</ul>
</nav>
<!-- 导航结束 -->
</div>
</div>
<!-- top结束 -->
# 4、第四步:美化导航
- 为了让鼠标移入导航文字上下区域和左右间隙也能有移入效果,我们需要给 a 标签添加行高 80px 。
- 行高 80px 也能让文字在垂直方向上距中对齐
.top .top-content {
/* 省略了部分css 具体见前面 */
/* 去掉 红色的背景 */
/* background-color: red; 后面会去掉 */
}
.top ul.menu li a {
display: block; /* 块级元素 */
line-height: 80px; /* 行高等于父元素的高度 文字垂直居中显示 */
padding-right: 16px; /* 右内边距 */
padding-left: 15px; /* 左内边距 */
color: var(--font-black-color);
font-size: 18px;
}
/* 鼠标移入改变文字颜色 */
.top ul.menu li a:hover {
color: var(--primary--color);
}
# 5、第五步:鼠标移入导航时,显示动态下划线
- 给
a
标签 或li
标签添加::after
伪元素,来实现动态下划线 - 当定位元素的
left
和right
属性值都为50%
时,相当于隐藏元素
ul.menu li {
position: relative; /* 开启定位,为伪元素定位做准备 */
}
/* 给 a 标签 或 li标签添加 ::after 伪元素,来实现动态下划线 */
ul.menu li::after {
content: "";
position: absolute;
/* 当定位元素的 left 和 right 属性值都为 50%时,相当于隐藏元素 */
left: 50%;
right: 50%;
bottom: 0;
height: 2px;
background-color: var(--primary--color);
transition: left 0.3s, right 0.3s; /* 为属性添加过渡动效果 */
}
/* 鼠标移入改变伪元素的宽 */
ul.menu li:hover::after {
left: 0;
right: 0;
}
# 6、第六步:实现二级下拉菜单
二级菜单中的 a 标签会继承 一级菜单中的padding: 0 16px 0px 15px;
所以在这一步中,要重置 a 标签的 padding 值。
<style>
ul.menu li .menu-dropdown {
position: absolute;
left: 0;
right: 0;
top: 80px;
background-color: var(--white-color);
transform-origin: top center; /* 设置变换的原点 顶部的中间 */
transform: scaleY(0); /* 初始状态为折叠 缩放到0 元素完全看不见 */
transition: transform 0.3s; /* 为属性添加过渡动效果 */
}
ul.menu li .menu-dropdown {
padding-top: 2px;
padding-bottom: 8px;
}
ul.menu li .menu-dropdown dd {
overflow: hidden;
}
.top ul.menu li .menu-dropdown dd a {
/* display: block; */
line-height: 39px;
padding: 0px; /* 重置 a 标签 继承的 样式*/
text-align: center;
}
.top ul.menu li:hover .menu-dropdown {
transform: scaleY(1); /* 初始状态为折叠 缩放到0 元素完全看不见 */
}
</style>
<!-- 导航开始 -->
<nav>
<ul class="menu">
<li><a href="">网站首页</a></li>
<li>
<a href="">国内游</a>
<dl class="menu-dropdown">
<dd><a href="#">北京</a></dd>
<dd><a href="#">三亚</a></dd>
<dd><a href="#">广东</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">云南</a></dd>
</dl>
</li>
<li>
<a href="">出镜游</a>
<dl class="menu-dropdown">
<dd><a href="#">泰国</a></dd>
<dd><a href="#">日本</a></dd>
<dd><a href="#">美国</a></dd>
<dd><a href="#">台湾</a></dd>
<dd><a href="#">海岛</a></dd>
</dl>
</li>
<li>
<a href="">周边游</a>
<dl class="menu-dropdown">
<dd><a href="#">北海</a></dd>
<dd><a href="#">桂林</a></dd>
<dd><a href="#">张家界</a></dd>
<dd><a href="#">凤凰</a></dd>
</dl>
</li>
<li>
<a href="">主题游</a>
<dl class="menu-dropdown">
<dd><a href="#">游学</a></dd>
<dd><a href="#">自组</a></dd>
</dl>
</li>
<li>
<a href="">自由行</a>
<dl class="menu-dropdown">
<dd><a href="#">三亚</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">丽江</a></dd>
</dl>
</li>
<li><a href="">合作案例</a></li>
<li><a href="">关于我们</a></li>
</ul>
</nav>
<!-- 导航结束 -->
# 7、鼠标移入二级菜单右移动画
- 利用 a 标签的伪类
::before
来制作 菜单前面的小竖线,一开始使用display:none
隐藏小竖线 - 当鼠标移入到 dd 元素上时,让 a 的伪类
::before
显示,同时,a 标签向右移动 10px - 给 a 标签加上 过渡动画,这样 a 标签向右移动时就有过渡动画效果
ul.menu li .menu-dropdown dd a {
/* 省略了部分css 具体见前面 */
position: relative; /* 开启定位,为伪元素定位做准备 */
transition: transform 0.5s; /* 为属性添加过渡动效果 */
}
/* 制作竖线 */
.menu-dropdown dd a::before {
content: "";
position: absolute;
top: 7px;
bottom: 7px;
left: 10px;
width: 2px;
background-color: var(--primary--color);
display: none; /* 初始状态隐藏 */
}
/* 鼠标移入 a标签向右移动 10px */
.menu-dropdown dd:hover a {
transform: translateX(10px);
}
/* 鼠标移入显示竖线 */
.menu-dropdown dd:hover a::before {
display: block;
}
# 8、实现吸顶盒导航效果
分析吸顶盒导航效果
- 刚开始进到页面,导航就显示在他原来的位置(浏览器的顶部)。
- 当浏览器的滚动条的滚动距离 > 1 时
- 元素先移出屏幕,然后再从屏幕是上方淡入移下来,最后固定在浏览器顶部
- 导航最外层容器背景变为白色,同时添加向下的阴影
吸顶盒导航实现原理
- 我们可以定义一个
sticky
class 类,来实现导航移入动画。
.sticky {
width: 100%; /* 这个宽度一定要加 ,元素定位后,为行内块元素特性 */
position: fixed;
top: 0;
box-shadow: 0 2px 5px #ddd; /* 添加阴影 */
background-color: var(--white-color); /* 背景颜色 */
animation: sticky-animation ease-out 0.5s both; /* 动画效果 */
}
/* 定义帧动画 */
@keyframes sticky-animation {
0% {
opacity: 0; /* 初始状态透明度为0 */
transform: translateY(-100%); /* 初始状态向上移动自身距离 */
}
100% {
opacity: 1; /* 结束状态透明度为1 */
transform: translateY(0); /* 结束状态向上移动0 */
}
}
- 当浏览器滚动条滚动距离 > 1 时,给
class='top'
的元素添加sticky
class ,实现盒子从上慢慢移下的效,否则,将sticky
class 从元素身上移除
<!-- top开始 -->
<!--
给 class="top" 元素添加 `id="J_top"` 方便我们通过 JS获取到该元素.
同时我们只要看到页面中元素的 id名是以大写字母 J_ 开头的,就知道当前元素有JS操作他。
-->
<div class="top" id="J_top">
<!--- 省略部分 html 结构 -->
</div>
<!-- top开始 -->
/* 实现吸顶盒导航 */
function stickyMenu() {
// 获取 top元素
const _top = document.getElementById("J_top");
// 监听浏览器的滚动事件
window.addEventListener("scroll", function () {
// 获取当前滚动条滚动的距离
let scrollTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop > 1) {
// 就给 top 元素添加 sticky 类名
_top.classList.add("sticky");
} else {
_top.classList.remove("sticky");
}
});
}
stickyMenu(); // 调用函数
# 9、实现导航响应式效果
当页面宽度 <=992px 时,导航隐藏,在最右侧显示 图标
- 通过媒体查询,在检测 页面视口宽 <=992px 时,将导航隐藏 (以下代码要放在所有 css 之后)
/* 以下 css 要写在所有页面样式之后 */
@media screen and (max-width: 992px) {
/* 最后是隐藏整个 nav 标签,而不只是 ul
.top ul.menu {
display: none
}
*/
.top nav {
display: none; /* 隐藏导航 */
}
}
- 在
class='logo'
元素的后面,添加 图标 - 图标采用
iconfont
图标
<!-- 引入字体图标样式 -->
<link rel="stylesheet" href="./iconfont/iconfont.css" />
<!-- 引入全局样式 -->
<style>
/* 控制栏目图标样式 */
.nav-button {
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
font-size: 24px;
margin-right: 10px;
display: none; /* 一开始隐藏图标 */
cursor: pointer;
}
</style>
<!-- logo开始 -->
<!-- 省略部分html ,具体见前面 -->
<!-- logo开始 -->
<!-- 栏目图标开始 -->
<div class="nav-button iconfont icon-lanmu"></div>
<!-- 栏目图标结束 -->
- 一开始图标是隐藏的,当 页面视口宽 <=992px 再将图标显示 ( 以下代码要放在所有 css 之后)
@media screen and (max-width: 992px) {
.top nav {
display: none; /* 隐藏导航 */
}
.nav-button {
display: block; /* 显示导航按钮 */
}
}
# 10、完整版代码
项目目录结构
response-web
├─ .gitignore
├─ css
│ ├─ global.css
│ ├─ index.css
│ ├─ media.css
│ ├─ reset.css
│ ├─ response.css
│ └─ skin-theme.css // 主题
├─ iconfont
│ ├─ demo.css
│ ├─ demo_index.html
│ ├─ iconfont.css
│ ├─ iconfont.js
│ ├─ iconfont.json
│ ├─ iconfont.ttf
│ ├─ iconfont.woff
│ └─ iconfont.woff2
├─ images // 图片内容省略
├─ index.html
├─ js
│ └─ menu.js
└─ README.md
index.html
网站首页
<!DOCTYPE html>
<!-- data-theme="yellow" -->
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>兼容多终端的响应式网站</title>
<!-- 清除标签默认样式 -->
<link rel="stylesheet" href="./css/reset.css" />
<!-- 主题皮肤样式表 -->
<link rel="stylesheet" href="./css/skin-theme.css" />
<!-- 字体图标样式表 -->
<link rel="stylesheet" href="./iconfont/iconfont.css" />
<!-- 全局样式表 -->
<link rel="stylesheet" href="./css/global.css" />
<!-- 首页页面样式表 -->
<link rel="stylesheet" href="./css/index.css" />
<!-- 媒体查询 响应式栅格系统样式表 -->
<link rel="stylesheet" href="./css/media.css" />
<link rel="stylesheet" href="./css/response.css" />
</head>
<body>
<header class="header">
<div class="header-content">
<!-- 网站头部开始 -->
<div class="top" id="J_top">
<div class="top-content layout">
<!-- logo开始 -->
<div class="logo">
<a href="#">
<img src="./images/logo.png" alt="艾编程logo" width="118" />
</a>
</div>
<!-- logo结束 -->
<!-- 栏目小图标 开始 -->
<div class="nav-button iconfont icon-lanmu"></div>
<!-- 栏目小图标 结束 -->
<!-- 导航开始 -->
<nav>
<ul class="menu">
<li>
<a href="">网站首页</a>
</li>
<li>
<a href="">国内游</a>
<dl class="menu-dropdown">
<dd><a href="#">北京</a></dd>
<dd><a href="#">三亚</a></dd>
<dd><a href="#">广东</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">云南</a></dd>
</dl>
</li>
<li>
<a href="">出镜游</a>
<dl class="menu-dropdown">
<dd><a href="#">泰国</a></dd>
<dd><a href="#">日本</a></dd>
<dd><a href="#">美国</a></dd>
<dd><a href="#">台湾</a></dd>
<dd><a href="#">海岛</a></dd>
</dl>
</li>
<li>
<a href="">周边游</a>
<dl class="menu-dropdown">
<dd><a href="#">北海</a></dd>
<dd><a href="#">桂林</a></dd>
<dd><a href="#">张家界</a></dd>
<dd><a href="#">凤凰</a></dd>
</dl>
</li>
<li>
<a href="">主题游</a>
<dl class="menu-dropdown">
<dd><a href="#">游学</a></dd>
<dd><a href="#">自组</a></dd>
</dl>
</li>
<li>
<a href="">自由行</a>
<dl class="menu-dropdown">
<dd><a href="#">三亚</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">丽江</a></dd>
</dl>
</li>
<li><a href="">合作案例</a></li>
<li><a href="">关于我们</a></li>
</ul>
</nav>
<!-- 导航结束 -->
</div>
</div>
<!-- 网站头部结束 -->
</div>
</header>
<!--js 代码一定要放在 /body 的最前-->
<script src="./js/menu.js"></script>
</body>
</html>
./css/skin-theme.css
不同主题样式
/* 主题皮肤定制 */
/* 默认主题写在上面 */
/* 绿色主题 */
:root,
html[data-theme="turquoise"] {
/* 主色调 */
--primary--color: #1ec28b; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
/* 黄色主题 */
html[data-theme="yellow"] {
/* 主色调 */
--primary--color: #ffd336; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
/* 深红色主题 */
html[data-theme="red"] {
/* 主色调 */
--primary--color: #ff383f; /* 主体色 */
--light-gray-color: #f4f4f6; /* 浅灰色 */
--white-color: #fff; /* 白色 */
/* 字体色 */
--font-black-color: #000; /* 黑色 */
--font-dark-grey-color: #666; /* 深灰色 */
--font-red-color: #ff0000; /* 红色 */
--font-white-color: #fff; /* 白色 */
/* 边框色 */
--border-dark-grey-color: rgba(28, 28, 27, 0.22); /* 深灰色边框 */
/* 辅助色 */
--sub-dark-red-color: #ff6347; /* 深红色 */
--sub-deep-orange-color: #ff8900; /* 深橘红色 */
/* 网站底部色 */
--footer-bg-color1: #2e2e2e; /* 网站底部背景色 */
--footer-bg-color2: #212121; /* 网站底部背景色 */
--footer-font-color: #d6d6d6; /* 网站底部背景色 */
}
./css/reset.css
清除标签默认样式
/* 清除默认样式*/
html,
body,
ul,
li,
dl,
dt,
dd {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
a {
text-decoration: none; /* 去掉 a标签默认的下划线 */
}
./css/global.css
全局通用样式
/* 全局通用样式 */
body {
font: 16px/1.5 "微软雅黑";
color: #000;
}
.layout {
max-width: 1200px; /* 最大宽度 */
margin: 0 auto;
}
./css/response.css
响应式样式
/* 当浏览器的宽度小于等于 992px */
@media screen and (max-width: 992px) {
/* 隐藏顶部导航 */
.top nav {
display: none;
}
/* 显示栏目图标 */
.nav-button {
display: block;
}
}
./css/index.css
首页样式
/* 当前项目首页用到的 css 样式 */
.header {
width: 100%;
height: 758px;
/* background-image: linear-gradient(
to bottom,
var(--primary--color),
var(--white-color)
); */
/* opacity: 0.4; 设置元素透明 */
position: relative; /* 相对定位 */
}
.header::before,
.header-content {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.header::before {
content: "";
background-image: linear-gradient(
to bottom,
var(--primary--color),
var(--white-color)
);
opacity: 0.4;
}
.top {
/* width:100%; */
height: 80px;
/* background-color: #ddd; */
/* border: 1px solid red; */
}
.top .top-content {
height: inherit;
/* background-color: red; */
display: flex;
align-items: center;
justify-content: space-between; /* 水平两端对齐 */
}
.top .top-content ul.menu {
display: flex;
}
/* 一级导航样式 */
ul.menu li a {
display: block;
line-height: 80px;
padding-right: 16px;
padding-left: 15px;
color: var(--font-black-color);
font-size: 18px;
/* border: 1px solid red; */
}
ul.menu li a:hover {
color: var(--primary--color);
}
ul.menu li {
position: relative; /* 相对定位 */
}
ul.menu li::after {
content: "";
position: absolute;
bottom: 0px;
left: 50%;
right: 50%;
height: 2px;
background-color: var(--primary--color);
transition: left 0.3s, right 0.3s;
}
ul.menu li:hover::after {
left: 0;
right: 0;
}
/* 二级下拉菜单 */
ul.menu li .menu-dropdown {
position: absolute;
left: 0;
right: 0;
top: 80px;
background-color: var(--white-color);
transform-origin: top center; /* 变换的原点在顶部的中心 */
transform: scaleY(0); /* y轴缩小到0 看不到了*/
transition: transform 0.3s; /* 过渡效果 */
}
ul.menu li .menu-dropdown {
padding-top: 2px;
padding-bottom: 8px;
}
ul.menu li .menu-dropdown dd {
overflow: hidden;
}
ul.menu li .menu-dropdown dd a {
line-height: 30px;
padding: 0px;
text-align: center;
/* border: 1px solid red; */
position: relative; /* 相对定位 */
transition: transform 0.5s;
}
ul.menu li:hover .menu-dropdown {
transform: scaleY(1); /* y轴放大到1 显示出正常大小 */
}
/* 绘制小竖线 */
.menu-dropdown dd a::before {
content: "";
position: absolute;
top: 7px;
bottom: 7px;
left: 10px;
width: 2px;
background-color: var(--primary--color);
display: none; /*隐藏*/
}
/* 鼠标移入 a标签向右移动 10px */
.menu-dropdown dd:hover a {
transform: translateX(10px);
}
/* 鼠标移入显示竖线 */
.menu-dropdown dd:hover a::before {
display: block;
}
.sticky {
width: 100%;
position: fixed;
top: 0;
background-color: var(--white-color);
box-shadow: 0 2px 5px var(--border-dark-grey-color);
/* 定义 animation 动画 */
animation: sticky-animation 0.5s ease-out both;
}
@keyframes sticky-animation {
0% {
opacity: 0; /* 透明的 */
transform: translateY(-100%); /* 向上移动自身的高度 */
}
100% {
opacity: 1; /* 不透明 */
transform: translateY(0); /* 向下移动到自身的高度 */
}
}
/* 栏目图标样式 */
.nav-button {
width: 34px;
/* background: red; */
margin-right: 10px;
font-size: 30px;
text-align: center;
display: none;
}
./js/menu.js
实现吸顶盒导航
function stickyMenu() {
// 获取 top元素
const _top = document.getElementById("J_top");
// 监听浏览器的滚动事件
window.addEventListener("scroll", function () {
// 获取当前滚动条滚动的距离
let scrollTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop > 1) {
// 就给 top 元素添加 sticky 类名
_top.classList.add("sticky");
} else {
_top.classList.remove("sticky");
}
});
}
stickyMenu(); // 调用函数
# 11、修复 bug 一
当页面宽度 <=1200px 时 ,logo 图标与浏览器左边添加 25px 的间距
/* 以下代码放入 response.css 文件中 */
@media screen and (max-width: 1200px) {
.logo {
margin-left: 25px;
/* 如果需要过渡自然的活,可以加上过渡动画 */
}
}
# 12、修复 bug 二
栏目小图标按设计稿要求,他与浏览器左边的间距为 30px
.nav-button {
/* 省略部分css -- */
margin-right: 30px; /* 这里的右边距为 30px */
cursor: pointer; /* 鼠标样式-手指形状 */
}
# 五、垂直二级导航开发
TIP
- 当浏览器屏幕缩小到
<=992px
时,水平导航会隐藏,从而在最右侧显示栏目小图标 。(这个功能我们在上一小节《网站顶部导航》已经实现了)。 - 点击右侧栏目小图标 ,会显示对应的二级菜单 ,效果如下
以下是具体的实现步骤
# 1、第一步:实现黑色半透明遮罩层
黑色半透明遮罩层特点
- 黑色半透明遮罩层的大小与浏览器屏幕一样大小,并且覆盖在页面其它内容的最上面。
- 当我们滚动浏览器的滚动条时,黑色半透明遮罩层位置始终相对于浏览器固定不动。
通过上面的分析,我们知道,黑色半透明遮罩层是相对于浏览器固定定位,定位在浏览器的左上角。
# 1.1、具体实现
TIP
- 创建一个 class 类名为
mask
的 div 作为 body 标签 的子元素,将其宽高设置与浏览器一样大小,同时背景为黑色半透明 - 利用
fixed
固定定位,将其定位在浏览器的左上角
<style>
/* 黑色半透明遮罩层 */
.mask {
position: fixed; /* 固定定位 */
/*
top 与 bottom 拉伸盒子高与浏览器一样高
left 与 right 拉伸盒子宽与浏览器一样高
*/
top: 0;
bottom: 0;
left: 0;
right: 0;
/* 这里的黑色半透明可以直接用 ` rgba(0,0,0,.5)` 实现 ,因为在任何主题下,遮罩层都是黑色的半透明的,不会被改成其它颜色*/
background-color: rgba(0, 0, 0, 0.3);
/* 一般我们会给黑色的半透明遮罩层添加 保证他会在的所有其它元素的最上面显示,不过在这个项目中,垂直菜单容器要在他上面显示*/
z-index: 99;
}
</style>
<!-- 黑色半透明遮罩层 开始 -->
<div class="mask"></div>
<!-- 黑色半透明遮罩层 结束 -->
# 2、第二步:制作垂直导航最外层黑色半透明容器
- 制作一个黑色的半透明遮罩层,宽
420px
,高同浏览器一样高。 - 利用固定定位,将遮罩层定位在浏览器顶部的最左边
温馨注意:
- 虽然
.mobile-nav
元素是放在绝对定位元素.header-content
中,但他还是在默认的 html 层叠上下文中。因为.header-content
是的 z-index 值是 auto,并不会创建自己的层叠上下文。 .mask
黑色半透明遮罩层是在 html 层叠上下文中,他写在</body>
的前面,所以会覆盖在.mobile-nav
黑色半透明遮罩层上面,我们需要给.mobile-nav
元素 设置z-index
来提升他的层级
<style>
/* 移动端或 ipad 端 导航*/
nav.mobile-nav {
width: 420px;
position: fixed; /* 固定定位 */
left: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 100; /* 提升层级,让他显示在黑色半透明遮罩层的上面,这里的值一定要大于 .mask 选择器中 z-index的值 */
}
</style>
<!-- 以下代码放在 class="header-content" div 容器中 -->
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav"></nav>
<!-- 移动 或 ipad端导航 结束-->
# 3、第三步、制作导航关闭按扭
- 制作一个宽高为
50px
的小正方形,相对父元素.mobile-nav
绝对定位,定位在与父元素上和右都为 30px 的位置 - 利用 iconfont 阿里图标,给元素添加 关闭按扭的图标,将图标颜色设为白色
温馨提示
这里一定要将 line-height
行高的值设为 1 ,否则就会继承 body
选择器中的 line-height
值 1.5
<style>
.colse-button {
position: absolute;
top: 30px;
right: 30px;
width: 50px;
height: 50px;
font-size: 50px;
line-height: 1; /* 记得将行高设为 1 */
color: #fff;
cursor: pointer;
}
</style>
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav">
<!-- 关闭按扭用的 iconfont 图标 -->
<span class="colse-button iconfont icon-chacha1"></span>
</nav>
<!-- 移动 或 ipad端导航 结束-->
# 4、第四步:垂直二级导航的显示与隐藏
- 一开始遮罩层和移动导航容器都是隐藏的
/* 黑色半透明遮罩层 */
.mask {
display: none;
}
/* 移动端或 ipad 端 导航*/
nav.mobile-nav {
display: none;
}
- 给栏目小图标添加
id="J_nav-icon"
方便 JS 获取该元素 - 给关闭按扭添加
id='J_close-button'
方便 JS 获取该元素 - 给全屏的黑色半透明遮罩层添加
id="J_mask"
,方便 JS 获取该元素 - 给垂直菜单添加
id="J_mobile-nav"
,方便 JS 获取该元素
<!-- 栏目小图标 开始 -->
<div class="nav-button iconfont icon-lanmu" id="J_nav-icon"></div>
<!-- 关闭按扭用的 iconfont 图标 -->
<span class="colse-button iconfont icon-chacha1" id="J_close-button"></span>
<!-- 黑色半透明遮罩层 开始 -->
<div class="mask" id="J_mask"></div>
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav" id="J_mobile-nav"></nav>
- JS 实现点击 栏目小图标显示菜单和黑色半透明遮罩层
- JS 实现点击关闭按扭,关闭 垂直导航和黑色半透明遮罩层
// 获取栏目小图标
const navIcon = document.getElementById("J_nav-icon");
// 获取关闭菜单按扭
const closeButton = document.getElementById("J_close-button");
// 获取遮罩层
const mask = document.getElementById("J_mask");
// 获取垂直导航
const mobileNav = document.getElementById("J_mobile-nav");
// 给栏目小图标添加点击事件
navIcon.addEventListener("click", function () {
// 显示黑色半 透明遮罩层
mask.style.display = "block";
// 显示菜单
mobileNav.style.display = "block";
});
// 给关闭菜单按扭添加点击事件
closeButton.addEventListener("click", function () {
// 关闭菜单
mask.style.display = "none";
mobileNav.style.display = "none";
});
# 5、第五步:添加一级菜单
- 设置菜单项与黑色遮罩层顶部,左边,右边的间距
- 设置每一个菜单项的字体样式 和 间距
<style>
/* 清除 h3 标签的默认外边距 */
h3 {
margin: 0;
padding: 0;
}
.nav-list {
padding-top: 92px;
margin: 0px 30px;
}
.nav-list li h3,
.nav-list li > a {
display: block;
font-size: 32px;
color: #fff;
font-weight: 400;
margin-bottom: 20px;
/* 行高 32 *1.5= 48px 通过行高 推出高为 48px */
/* height: 48px; */
cursor: pointer;
}
</style>
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav">
<span class="colse-button iconfont icon-chacha1"></span>
<ul class="nav-list">
<li><a href="#">首页</a></li>
<li><h3>国内游</h3></li>
<li><h3>出镜游</h3></li>
<li><h3>周边游</h3></li>
<li><h3>主题游</h3></li>
<li><h3>自由行</h3></li>
<li><h3>合作案例</h3></li>
<li><h3>关于我们</h3></li>
</ul>
</nav>
<!-- 移动 或 ipad端导航 结束-->
# 6、第六步:为一级菜单添加向右的箭头
- 利用 span 标签 和 iconfont 图标来实现向右的箭头。
- 箭头相对于父元素绝对定位,定位到距离父元素右侧 31px ,垂直居中位置
<style>
.nav-list li h3 {
position: relative; /* 相对定位 */
}
.nav-list li h3 span.arrow {
/* 不要设置宽和高 阿里图标形成的图片本身占居空间是一个正方形*/
position: absolute; /* 绝对定位 */
right: 31px;
top: 50%;
transform: translateY(-50%);
font-size: 28px;
line-height: 1; /* 行高为 1 */
}
</style>
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav">
<span class="colse-button iconfont icon-chacha1"></span>
<ul class="nav-list">
<li><a href="#">首页</a></li>
<li>
<h3>
国内游
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
</li>
<li>
<h3>出镜游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>周边游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>主题游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>自由行<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li><h3>合作案例</h3></li>
<li><h3>关于我们</h3></li>
</ul>
</nav>
<!-- 移动 或 ipad端导航 结束-->
# 7、第七步:二级菜单展开始,箭头朝下的实现原理
当点击按扭,展开二级菜单时,我们给 span
标签再添加一个为 arrow-up
的 class 类名,这个类名用来控制箭头向下。
<style>
.nav-list li h3 span.arrow {
/* 省略部分 CSS 样式,具体见前面*/
transition: transform 0.3s; /* 添加过渡动画 */
}
.nav-list li h3 span.arrow-down {
/* 在原来位移的基础上,再顺时间旋转 90deg */
transform: translateY(-50%) rotate(90deg);
}
</style>
<h3>
国内游
<span class="arrow iconfont icon-youjiantou2 arrow-down"></span>
</h3>
# 8、第八步:添加二级菜单
温馨提示
在一级菜单中,我们给 a 标签添加的样式,会影响到这里,所以记得去做相关修改
<style>
/* 垂直二级菜单 */
.mobile-nav .nav-list li .menu-dropdown dd a {
display: block;
height: 64px;
line-height: 64px;
font-size: 32px;
margin-bottom: 0px;
color: #fff;
text-indent: 64px;
}
.mobile-nav .nav-list li .menu-dropdown dd a:hover {
background-color: var(--primary-color);
}
</style>
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav">
<span class="colse-button iconfont icon-chacha1"></span>
<ul class="nav-list">
<li><a href="#">首页</a></li>
<li>
<h3>
国内游
<span class="arrow iconfont icon-youjiantou2 arrow-down"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">北京</a></dd>
<dd><a href="#">三亚</a></dd>
<dd><a href="#">广东</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">云南</a></dd>
</dl>
</li>
<li>
<h3>出镜游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>周边游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>主题游<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li>
<h3>自由行<span class="arrow iconfont icon-youjiantou2"></span></h3>
</li>
<li><h3>合作案例</h3></li>
<li><h3>关于我们</h3></li>
</ul>
</nav>
<!-- 移动 或 ipad端导航 结束-->
# 9、第九步:二级菜单展开 和 收缩 动画的实现原理
- 当点击一级菜单,会向下慢慢展开显示二级菜单,本质是在不断的改变
li
元素的高度。 - 一开始,**每个 li 的高度= h3 元素高 +
margin-bottom
值 ** ,然后针对溢出 li 部分隐藏。 - 当点击一级菜单时,动态计算当前
li
的高度,然后修改 li 的高度。
每个 li 展开后高 = h3 元素高 + 20px 的 margin-bottom + dl 元素的占位高
li 展开后的高度,需要通过 JS 来自动计算。
.nav-list li {
height: 68px;
overflow: hidden;
transition: height 0.3s; /* 为高度属性添加过渡动画 */
}
<!-- 移动 或 ipad端导航 开始-->
<nav class="mobile-nav">
<span class="colse-button iconfont icon-chacha1"></span>
<ul class="nav-list">
<li><a href="#">首页</a></li>
<li>
<h3>
国内游
<span class="arrow iconfont icon-youjiantou2 arrow-down"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">北京</a></dd>
<dd><a href="#">三亚</a></dd>
<dd><a href="#">广东</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">云南</a></dd>
</dl>
</li>
<li>
<h3>出镜游<span class="arrow iconfont icon-youjiantou2"></span></h3>
<dl class="menu-dropdown">
<dd><a href="#">泰国</a></dd>
<dd><a href="#">日本</a></dd>
<dd><a href="#">美国</a></dd>
<dd><a href="#">台湾</a></dd>
<dd><a href="#">海岛</a></dd>
</dl>
</li>
<li>
<h3>周边游<span class="arrow iconfont icon-youjiantou2"></span></h3>
<dl class="menu-dropdown">
<dd><a href="#">北海</a></dd>
<dd><a href="#">桂林</a></dd>
<dd><a href="#">张家界</a></dd>
<dd><a href="#">凤凰</a></dd>
</dl>
</li>
<li>
<h3>主题游<span class="arrow iconfont icon-youjiantou2"></span></h3>
<dl class="menu-dropdown">
<dd><a href="#">游学</a></dd>
<dd><a href="#">自组</a></dd>
</dl>
</li>
<li>
<h3>自由行<span class="arrow iconfont icon-youjiantou2"></span></h3>
<dl class="menu-dropdown">
<dd><a href="#">三亚</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">丽江</a></dd>
</dl>
</li>
<li><h3>合作案例</h3></li>
<li><h3>关于我们</h3></li>
</ul>
</nav>
<!-- 移动 或 ipad端导航 结束-->
# 10、第十步:JS 实现二级菜单特效 - 事件委托
TIP
- 当我们点击 h3 标签时,才会展开和收缩二级菜单,不过这里我们不打算给 h3 绑定点击事件。
- 我们采用事件委托,将 h3 的点击事件委托给到他们共同的父元素 ul 来实现。
具体实现步骤
- 给
<ul class="nav-list">
添加id="J_mobile-nav-list"
方便通过 Id 名来获取 ul 元素
<ul class="nav-list" id="J_mobile-nav-list">
<!-- 省略更多的 html 标签 -->
</ul>
- 采用事件代理,将 h3 的点击事件委托给到 ul
// 获取 ul 元素
const mobileNavList = document.getElementById("J_mobile-nav-list");
// 添加点击事件
mobileNavList.addEventListener("click", function (e) {
const target = e.target; // 获取触发事件的元素
// 如果触发事件的元素标签名不是 h3 则 啥也不做
if (target.tagName.toLowerCase() !== "h3") return;
// 如果触发事件的元素标签名是 h3,则执行这行注释之后的代码
});
# 11、第十一步: 如何判断当前菜单是展开还是折叠的
TIP
我们需要给每项菜单的 li 或 h3 标签的 S 对象添加一个属性 flag ,这个属性记录着当前的菜单状态
- 如果
flag 值为 false
则表示当前菜单折叠 - 如果
flag 值为 true
则表示当前菜单是展开的
// 获取 ul 元素
const mobileNavList = document.getElementById("J_mobile-nav-list");
// 添加点击事件
mobileNavList.addEventListener("click", function (e) {
const target = e.target; // 获取触发事件的元素
if (target.tagName.toLowerCase() !== "h3") return;
// ---------------------- 新增JS ----------------------------------
// 如果 target.flag 值为 false 或 undefined
if (!target.flag) {
// 当前菜单是折叠的,则可以展开
// 更新菜单状态
target.flag = true;
alert("可以展开");
// 菜单展开,本质就是动态修改 li 的高度,
// 同给 span.arrow 元素添加 class 类名 arrow-down
} else {
// 当前菜单是展开的,可以折叠
// 更新菜单状态
target.flag = false;
alert("可以折叠");
}
});
# 12、第十二步:实现菜单展开和折叠
菜单展开 :
通过 JS 计算当前 li 展开后的高度 ,然后修改当前 li 的高
- li 展开后高 = h3 的高 + h3 元素的 margin-bottom 值 + dl 元素的高(dl 元素没有外边距)
h3 标签中的 span 元素添加 class 类名
arrow-down
,实现 箭头向下
菜单收缩:
- li 收缩后高度 = h3 的高 + h3 元素的 margin-bottom 值
- 移除 h3 标签中的 span 元素的 class 类名
arrow-down
,实现箭头向右
// 获取 ul 元素
const mobileNavList = document.getElementById("J_mobile-nav-list");
// 添加点击事件
mobileNavList.addEventListener("click", function (e) {
const target = e.target; // 获取触发事件的元素
if (target.tagName.toLowerCase() !== "h3") return;
// 如果 target.flag 值为 false 或 undefined
if (!target.flag) {
// 当前菜单是折叠的,则可以展开
// 更新菜单状态
target.flag = true;
// ---------------------- 新增JS ----------------------------------
// 获取 h3 标签可视高 (包括 height +padding +border)
const h3Height = target.offsetHeight;
// 获取 h3 标签的向下外边距
const marginBottom = parseInt(window.getComputedStyle(target).marginBottom);
// 获取当前菜单二级菜单 dl 可视高 (dl 没有外边距)
const dlHeight = target.nextElementSibling.offsetHeight;
// 计算得到当前 li的高度
const liHeight = h3Height + marginBottom + dlHeight;
// 更新当前菜单 li 的高度
target.parentNode.style.height = `${liHeight}px`;
} else {
// 当前菜单是展开的,可以折叠
// 更新菜单状态
target.flag = false;
// ---------------------- 新增JS ----------------------------------
// 获取 h3 标签可视高 (包括 height +padding +border)
const h3Height = target.offsetHeight;
// 获取 h3 标签的向下外边距
const marginBottom = parseInt(window.getComputedStyle(target).marginBottom);
// 计算得到当前 li的高度
const liHeight = h3Height + marginBottom;
// 更新当前菜单 li 的高度
target.parentNode.style.height = `${liHeight}px`;
}
});
# 13、第十三步:垂直二级导航移动端适配
移动端二级导航的各种尺寸与 ipad 端不同,我们有以下两种方式来适配
# 13.1、适配方式一
TIP
通过媒体查询来检测当前视口宽,如果视口宽<=576px 时,则重新定义 CSS 的属性值
/* 以下 css 放在 response.css 文件中 */
/* 当页面宽度小于 576px 时,对应的菜单样式 */
@media screen and (max-width: 576px) {
/* 垂直导航容器- ipad端或移动端导航*/
nav.mobile-nav {
width: 280px;
}
/* 关闭按扭 */
.close-button {
width: 30px;
height: 30px;
font-size: 30px;
}
.nav-list {
padding-top: 54px;
margin: 0 30px;
}
.nav-list li h3,
.nav-list li a {
font-size: 18px;
/* height:27px; */
/* 行高 1.5 * 18 = 27px 向上 4 或 5
*/
margin-bottom: 10px;
}
.nav-list li {
height: 37px; /* 导航收缩时的高度*/
}
/*
需要动态的计算当前 li的高度
收缩时 li高度 = h3 高度 + h3 的margin-bottom =64px
展开时 li高度=h3 高度 + h3 的margin-bottom + dl可视区高
*/
/* 箭头样式 */
.nav-list li h3 span.arrow {
/* border: 1px solid red; */
font-size: 17px;
right: 29px;
}
/* 移动端 二级导航样式 */
.mobile-nav .menu-dropdown dd a {
display: block;
height: 36px;
line-height: 36px;
text-indent: 35px;
}
.mobile-nav .menu-dropdown {
/* 27-9-4 */
padding-bottom: 14px;
}
}
# 13.2、适配方式二
- 在 :root 选择器中,将不同的属性值,定义成一套 CSS 自定义属性
/* 以下 css 放在 skin-theme.css 文件中 */
:root {
--nav-width: 420px; /* 导航容器宽 */
--close-button-width: 50px; /* 关闭按钮宽 */
--close-button-height: 50px; /* 关闭按钮高 */
--close-button-font-size: 50px; /* 关闭按钮字体大小 */
--close-button-right: 30px; /* 关闭按钮距父元素右距离 */
--close-button-top: 30px; /* 关闭按钮距父元素上距离 */
--nav-top: 92px; /* 整个菜单距顶部距离 */
--nav-margin-left: 30px; /* 整个菜单左外边距 */
--nav-margin-right: 30px; /* 整个菜单右外边距*/
--menu-item-font-size: 32px; /* 菜单项字体大小 */
--menu-item-margin-bottom: 16px; /* 菜单项底部外边距 */
--menu-item-height: 64px; /* 菜单项高 */
--arrow-font-size: 28px; /* 箭头字体大小 */
--arrow-right: 41px; /* 箭头距父元素右距离 */
--submenu-item-height: 64px; /* 子菜单项高 */
--submenu-item-font-size: 32px; /* 子菜单项字体大小 */
--submenu-item-text-indent: 60px; /* 子菜单项文本缩进 */
--submenu-item-padding-bottom: 16px; /* 子菜单项底部外边距 */
}
- 通过媒体查询来检测当前视口宽,如果视口宽<=576px 时,则重新定义一套 CSS 自定义属性
/* 以下 css 放在 skin-theme.css 文件中 */
@media screen and (max-width: 576px) {
:root {
--nav-width: 280px;
--close-button-width: 30px; /* 关闭按钮宽 */
--close-button-height: 30px; /* 关闭按钮高 */
--close-button-font-size: 30px; /* 关闭按钮字体大小 */
--close-button-right: 30px; /* 关闭按钮距父元素右距离 */
--close-button-top: 30px; /* 关闭按钮距父元素上距离 */
--nav-top: 54px;
--nav-margin-left: 30px; /* 整个菜单左外边距 */
--nav-margin-right: 30px; /* 整个菜单右外边距*/
--menu-item-font-size: 18px;
--menu-item-margin-bottom: 10px; /* 菜单项底部外边距 */
--menu-item-height: 37px; /* 菜单项高 */
--arrow-font-size: 17px; /* 箭头字体大小 */
--arrow-right: 29px;
--submenu-item-height: 36px; /* 子菜单项高 */
--submenu-item-line-height: 36px; /* 子菜单项高 */
--submenu-item-font-size: 18px; /* 子菜单项字体大小 */
--submenu-item-text-indent: 35px; /* 子菜单项文本缩进 */
--submenu-item-padding-bottom: 14px; /* 子菜单项底部外边距 */
}
}
- 将 CSS 代码中的属性值替换为
var
函数引用 CSS 自定义属性值
以下 CSS 和 上面的 自定义 CSS 属性 是当前效果完整 CSS
/* 黑色半透明遮罩层样式 */
.mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99; /* 遮罩层在最上层 */
display: none; /* 隐藏*/
}
/* 垂直导航容器- ipad端或移动端导航*/
nav.mobile-nav {
/* width: 420px; */
width: var(--nav-width);
position: fixed;
left: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100; /* 导航要在遮罩层的上面 */
display: none; /*隐藏*/
}
/* 关闭按扭 */
.close-button {
display: block;
width: var(--close-button-width);
height: var(--close-button-height);
font-size: var(--close-button-font-size);
/* width: 50px; */
/* height: 50px; */
/* font-size: 50px; */
line-height: 1;
/* background-color: red; */
color: #fff;
position: absolute;
/* right: 30px; */
right: var(--close-button-right);
/* top: 30px; */
top: var(--close-button-top);
cursor: pointer;
}
.nav-list {
/* padding-top: 92px; */
padding-top: var(--nav-top);
/* margin: 0 30px; */
margin: 0 var(--nav-margin-right) 0 var(--nav-margin-left);
}
.nav-list li h3,
.nav-list li a {
display: block;
/* font-size: 32px; */
font-size: var(--menu-item-font-size);
/* height:48px; */
/* 行高 1.5 * 32 = 48px; (48-32)/2= 8
32-16=16px
*/
/* margin-bottom: 16px; */
margin-bottom: var(--menu-item-margin-bottom);
color: #fff;
font-weight: 400;
cursor: pointer;
}
.nav-list li {
/* height: 64px; 导航收缩时的高度 */
height: var(--menu-item-height);
overflow: hidden;
transition: height 0.3s; /* 过渡动画 */
}
/*
需要动态的计算当前 li的高度
收缩时 li高度 = h3 高度 + h3 的margin-bottom =64px
展开时 li高度=h3 高度 + h3 的margin-bottom + dl可视区高
*/
.nav-list li h3 {
position: relative;
}
/* 箭头样式 */
.nav-list li h3 span.arrow {
/* border: 1px solid red; */
/* font-size: 28px; */
font-size: var(--arrow-font-size);
line-height: 1;
position: absolute;
/* right: 41px; */
right: var(--arrow-right);
top: 50%;
transform: translateY(-50%);
}
/* 向下箭头的样式 */
.nav-list li h3 span.arrow-down {
transform: translateY(-50%) rotate(90deg);
}
/* 移动端 二级导航样式 */
.mobile-nav .menu-dropdown dd a {
display: block;
/* height: 64px; */
height: var(--submenu-item-height);
/* line-height: 64px; */
line-height: var(--submenu-item-line-height);
/* text-indent: 60px; */
text-indent: var(--submenu-item-text-indent);
font-size: var(--submenu-item-font-size);
margin-bottom: 0;
}
.mobile-nav .menu-dropdown {
/* padding-bottom: 12px; */
padding-bottom: var(--submenu-item-padding-bottom);
}
.mobile-nav .menu-dropdown dd a:hover {
background-color: var(--primary--color);
}
# 14、完整的源码
<!-- ipad端或移动端导航 开始-->
<nav class="mobile-nav" id="J_mobile-nav">
<span class="close-button iconfont icon-chacha1" id="J_close-button"></span>
<ul class="nav-list" id="J_moible-nav-list">
<li><a href="#">首页</a></li>
<li>
<h3>
国内游
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">北京</a></dd>
<dd><a href="#">三亚</a></dd>
<dd><a href="#">广东</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">云南</a></dd>
</dl>
</li>
<li>
<h3>
出镜游
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">泰国</a></dd>
<dd><a href="#">日本</a></dd>
<dd><a href="#">美国</a></dd>
<dd><a href="#">台湾</a></dd>
<dd><a href="#">海岛</a></dd>
</dl>
</li>
<li>
<h3>
周边游
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">北海</a></dd>
<dd><a href="#">桂林</a></dd>
<dd><a href="#">张家界</a></dd>
<dd><a href="#">凤凰</a></dd>
</dl>
</li>
<li>
<h3>
主题游
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">游学</a></dd>
<dd><a href="#">自组</a></dd>
</dl>
</li>
<li>
<h3>
自由行
<span class="arrow iconfont icon-youjiantou2"></span>
</h3>
<dl class="menu-dropdown">
<dd><a href="#">三亚</a></dd>
<dd><a href="#">厦门</a></dd>
<dd><a href="#">丽江</a></dd>
</dl>
</li>
<li><a href="#">合作案例</a></li>
<li><a href="#">关于我们</a></li>
</ul>
</nav>
<!-- ipad端或移动端导航 结束-->
<!-- 黑色半透明遮罩层开始 -->
<div class="mask" id="J_mask"></div>
<!-- 黑色半透明遮罩层结束-->
subMenu();
function subMenu() {
// 获取栏目小图标
const navIcon = document.getElementById("J_nav-icon");
// 获取遮罩层
const mask = document.getElementById("J_mask");
// 获取垂直导航容器
const mobileNav = document.getElementById("J_mobile-nav");
// 获取关闭按钮
const closeButton = document.getElementById("J_close-button");
// 绑定一个点击事件
navIcon.addEventListener("click", function () {
// 让遮罩层显示
mask.style.display = "block";
// 让垂直菜单显示
mobileNav.style.display = "block";
});
// 给关闭按钮绑定一个点击事件
closeButton.addEventListener("click", function () {
// 让遮罩层隐藏
mask.style.display = "none";
// 让垂直菜单隐藏
mobileNav.style.display = "none";
});
/* 实现二级伸缩菜单 */
// 采用事件委托的方式
// 首先要获取 ul 元素
const mobileNavList = document.getElementById("J_moible-nav-list");
// 给 ul 添加 click 点击事件
mobileNavList.addEventListener("click", function (e) {
// 获取触发事件的元素
const target = e.target;
// 判断触发事件的元素的标签名是不是 h3
if (target.tagName.toLowerCase() !== "h3") return;
// 如果是 h3标签,则执行后面的代码
// 我要给 h3 标签(js对象)添加一个属性,用来记录当前菜单状态
// flag 如果 flag 的值是 false 表示菜单是收缩的
// 如果 flag 的值是 true 表示菜单是展开的
// 没有属性时 target.flag 就是 undefined
if (!target.flag) {
// 当前菜单是收缩的,那我们就可以展开他
// 更改菜单的状态
target.flag = true; // 把菜单的状态改为展开
// 实现菜单的展开效果
// 展开的本质就是动态的计算 li 的高度
/*
需要动态的计算当前 li的高度
收缩时 li高度 = h3 高度 + h3 的margin-bottom =64px
展开时 li高度=h3 高度 + h3 的margin-bottom + dl可视区高
*/ // 获取h3的可视高(height+padding+border)
const h3Height = target.offsetHeight;
// 获取h3的 margin-bottom
const h3MarginBottom = parseInt(
window.getComputedStyle(target).marginBottom
);
// 还要获取二级菜单 dl的可视高
const dlHeight = target.nextElementSibling.offsetHeight;
// 计算当前li的高度
const liHeight = h3Height + h3MarginBottom + dlHeight;
// 设置当前li的高度
target.parentNode.style.height = liHeight + "px";
} else {
// 当前菜单是展开的
target.flag = false; // 把菜单的状态改为收缩
const h3Height = target.offsetHeight;
// 获取h3的 margin-bottom
const h3MarginBottom = parseInt(
window.getComputedStyle(target).marginBottom
);
// 计算当前li的高度
const liHeight = h3Height + h3MarginBottom;
// 设置当前li的高度
target.parentNode.style.height = liHeight + "px";
}
});
}
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X