JavaScript 使用数据驱动方式来制作折叠菜单
最近想做一个个人博客系统,在写前端的过程中想做一个移动端的折叠菜单
本来已经做好的但是看一下自己写的代码都没有屎壳郎爬的好看果断选择重写
然后思来想去就想起来了Vue中有个叫数据驱动的方式然后就想尝试一下就有了下面的东西
这是一个移动端页面,从下面演示动画中你可能只能够看到一个简简单单的折叠菜单
BUT!这不是一个普通的折叠菜单,它是由数据驱动方式制作出来的
先来做一下对比
传统的折叠菜单
传统的折叠菜单只需要一个按钮,一个菜单容器,将初始状态写入css中,并通过JavaScript 对象记录下来当前状态.当需要展开的时候只需要通过改变添加或者删除对应的css就可以得到展开和关闭菜单的效果,改变按钮菜单的样式也是异曲同工之妙.
页面结构
<body>
<div class="left">
<div class="header">
<div class="header-in">
<div class="header-title">
<div id="mycard-switch" class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" >
</div>
<div class="title">
<h1> Pei Jingbo</h1>
<h2 class="subtitle">
副标题
</h2>
</div>
<div id="nav-btn" class="nav-btn iconfont icon-liebiao3 "></div>
</div>
<ul id="nav" class="nav">
<li class="nav-item">
<a href="#">
<i class="nav-item-icon nav-item-icon-action"></i>
<span class="nav-item-title">主页</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon "></i>
<span class="nav-item-title">分类</span>
</a>
</li>
<li class="nav-item nav-item-action">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">文章</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">关于</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">留言</span>
</a>
</li>
</ul>
</div>
<div id="mycard" class="my-card ">
<div class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" >
</div>
<div class="my-card-info">
<h1 class="name">
PeiJingbo
</h1>
<ul class="info">
<li class="info-item">
<span class="data">11245</span>
<span class="name">日志</span>
</li>
<li class="info-item">
<span class="data">10</span>
<span class="name">分类</span>
</li>
<li class="info-item">
<span class="data">500</span>
<span class="name">留言</span>
</li>
</ul>
<ul class="media">
<li class="media-item">
<a target="_blank" href="https://space.bilibili.com/125931368">
<i class="iconfont icon-bilibili-line "></i>
<span class="media-name">BILIBILI</span>
</a>
</li>
<li class="media-item">
<a target="_blank" href="https://github.com/PeiJingbobo">
<i class="iconfont icon-github "></i>
<span class="media-name">GitHub</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
BUT! 我们今天的需求略微有些复杂,我们可以看到网页左上角还有一个小头像的图标,我想通过点击这个图标来得到展开个人卡片的效果,然后通过右上角的关闭按钮进行关闭
根据上面的要求我们屡一下思路然后写出代码
// 导航栏展开关闭按钮
let nav_btn = document.getElementById("nav-btn");
// 导航栏本栏
let nav = document.getElementById("nav");
// 我的卡片开关按钮(header中的小头像)
let mycard_switch = document.getElementById("mycard-switch")
// 我的卡片本片
let mycard = document.getElementById("mycard")
let headerStatus = {
"navIsShow": false,
"mycardIsShow" : false,
"navBtnStaatus": true,
}
nav_btn.onclick = function (e){
// headerStatus
// if(headerStatus.mycardIsShow)
// {
// headerStatus.mycardIsShow = !headerStatus.mycardIsShow
// return
// }
navMenuChange()
}
function mycardSwitchEvent(e){
myCardChange()
}
mycard_switch.onclick = mycardSwitchEvent
function navMenuChange(){
if(headerStatus.navIsShow){
// 说明现在展开着呢 应该立即关闭菜单
nav.classList.remove("nav-btn-active")
// 菜单按钮设为关闭状态
navBtnChange()
}else {
// 应该立即展开
nav.classList.add("nav-btn-active")
navBtnChange()
}
headerStatus.navIsShow = !headerStatus.navIsShow
}
function myCardChange(){
if(headerStatus.mycardIsShow){
// 立即关闭
mycard.classList.remove("my-card-show")
mycard_switch.classList.remove("nav-btn-fade-out")
mycard_switch.classList.add("nav-btn-fade-in")
mycard_switch.onclick = mycardSwitchEvent
// 手动修改按钮样式
navBtnChange()
}else{
// 立即展开
mycard.classList.add("my-card-show")
// 头像隐藏
mycard_switch.classList.add("nav-btn-fade-out")
mycard_switch.onclick = null
navBtnChange()
}
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
}
function navBtnChange(){
// 如果是true表示需要变为x
if(headerStatus.navBtnStaatus){
console.log("显示关闭按钮")
nav_btn.classList.add("nav-btn-fade-out")
nav_btn.classList.remove("icon-liebiao3")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-out")
nav_btn.classList.add("nav-btn-fade-in")
},500)
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-in")
nav_btn.classList.add("icon-guanbi")
},500)
}else {
console.log("显示菜单按钮")
nav_btn.classList.remove("icon-guanbi")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-out")
nav_btn.classList.add("nav-btn-fade-in")
},500)
setTimeout(function (){
nav_btn.classList.add("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}
headerStatus.navBtnStaatus = !headerStatus.navBtnStaatus
}
所有HTML/CSS代码见文章末尾
演示一下BUG们
这样一看是没有什么问题但是当我们要关闭个人卡片的时候就会惊奇的发现有什么奇怪的事情已经发生了
这是因为我们的按钮点击事件只有关闭菜单的而没有关闭个人卡片的
nav_btn.onclick = function (e){
navMenuChange()
}
我们可以采取一个非常简单的判断来改善一下
nav_btn.onclick = function (e){
if(headerStatus.mycardIsShow)
{
myCardChange()
return
}
navMenuChange()
}
第一个BUG是已经修复了 但是下面有有了新问题,当我们展开菜单栏后我们再次点击头像应该是关闭菜单栏然后再展开个人卡片但是现在两个同时展开了 而且展开时菜单栏按钮样式不对
function mycardSwitchEvent(e){ // 小头像点击事件
myCardChange()
// 如果菜单正在打开则关闭
if(headerStatus.navIsShow){
navMenuChange()
}
}
看一下效果
!
???
【震惊】某程序员居然在写代码时发生灵异事件差点吓了个半死
虽然判断依据加上了但是效果一点没变,我一开始以为是浏览器缓存原因所以清了缓存
情况依然没有丝毫改变,通过浏览器的调试我发现问题出在了
}
navBtnChange()
}
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
}
和
}
headerStatus.navIsShow = !headerStatus.navIsShow
}
这个JavaScript 就很神奇 我感觉重复的代码应该放到最后面去执行,但是我万万没有想到的是 如果放到了最后的话 在 onclick中读取的值还是没有被改变的值,这就有点耐人寻味了
所有果断改到if和else执行的代码中间.这样不会有问题了吧
function myCardChange(){
if(headerStatus.mycardIsShow){
// 立即关闭
mycard.classList.remove("my-card-show")
mycard_switch.classList.remove("nav-btn-fade-out")
mycard_switch.classList.add("nav-btn-fade-in")
mycard_switch.onclick = mycardSwitchEvent
// 手动修改按钮样式
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
navBtnChange()
}else{
// 立即展开
mycard.classList.add("my-card-show")
// 头像隐藏
mycard_switch.classList.add("nav-btn-fade-out")
mycard_switch.onclick = null
// 虽然是重复的语句但是不能放在外面 这样浏览器执行的顺序就不一样 就不能再onclick中判断这个值了
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
if(!headerStatus.navBtnStaatus){
return
}
navBtnChange()
}
}
function navMenuChange(){
if(headerStatus.navIsShow){
// 说明现在展开着呢 应该立即关闭菜单
nav.classList.remove("nav-btn-active")
headerStatus.navIsShow = !headerStatus.navIsShow
if(!headerStatus.mycardIsShow){
navBtnChange()
}
}else {
// 应该立即展开
nav.classList.add("nav-btn-active")
headerStatus.navIsShow = !headerStatus.navIsShow
navBtnChange()
}
}
终于 终于 没有bug了
总结一下:
传统的折叠菜单实现方式是通过事件驱动方式来响应用户操作的,比如我们要展开和关闭导航菜单我们需要监听导航菜单按钮,然后通过按钮的点击事件去调用菜单切换的功能
这样操作无疑是简单快捷的.BUT! 现在我们只是兼顾了两个元素的显示与隐藏.那如果是五个,八个呢?
这样程序在逻辑上各种函数调用和嵌套就会带来很多意想不到的BUG
就好烦!
接下来接入正题,那什么是数据驱动 的方式呢
简单说,数据驱动就是你去改变某个数据,页面上的内容就会自动根据你所改变数据的样子去变化
如果有了解过Vue
的话就可以想到 vue
的data()就是这个道理
假如说你就是一个数据,简单的说,就是有一个叫做数据驱动的家伙它天天盯着你.但凡是你和外部其他人有来往它都会告诉另外一个人来做一些改变
那到底如何做到监听呢?
其实就要用到defineProperty
这个东西
有关defineProperty
的一切请点击这个链接
这个方法是用来定义对象的属性的一些信息的,它传入三个东西
-
对象(对象)
-
方法名(字符串)
-
属性描述符(对象)
它有
configurable
enumerable
value
writable
get
set
几个属性当同时具有
configurable
enumerable
value
writable
时它称之为数据描述符
数据描述符可以被赋值和读取
当同时具有
configurable
enumerable
get
set
时 它被称为存取描述符这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用
Object.defineProperty()
定义属性时的默认值):-
configurable
当且仅当该属性的
configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false
。 -
enumerable
当且仅当该属性的
enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。 默认为false
。
数据描述符还具有以下可选键值:
-
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为
undefined
。 -
writable
当且仅当该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被赋值运算符
改变。 默认为false
。
存取描述符还具有以下可选键值:
-
get
属性的 getter 函数,如果没有 getter,则为
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为undefined
。 -
set
属性的 setter 函数,如果没有 setter,则为
undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。 默认为undefined
。
configurable
enumerable
value
writable
get
set
数据描述符 可以 可以 可以 可以 不可以 不可以 存取描述符 可以 可以 不可以 不可以 可以 可以 -
当你读完上面链接里的内容你一定大致理解了这个是个什么东西
现在,我们想监听一个对象属性的变化,我们就要用get
和set
方法
下面来个实验:
let A = {
"a": 11
}
Object.defineProperty(A,"a",{
get: function() {
console.log("数据被获取了")
return 199
},
set:function(value){
console.log("数据被修改了",value)
}})
A.a
A.a = 13
A.a
他会出现下面这个结果
当我们的属性描述符有set
或者get
的时候,那么他就是一个存取描述符
存取描述符不能被读取 ,但是当有代码要读取他的内容时他就会执行get
方法中的内容
get
的返回值就是就是被拿到的值 但是因为是存取描述符,是不能被重新赋值的
所以我们如果想保存这个新给的值需要另外一个属性来存储
那就是数据描述符
let A = {
"a": 11,
"_a": null
}
Object.defineProperty(A,"a",{
get: function() {
console.log("数据被获取了")
return A._a
},
set:function(value){
A._a = value
console.log("数据被修改了",value)
}})
A.a
A.a = 13
A.a
需要注意的是,属性描述符的默认值是可以继承的,我们直接定义的属性已经有了属性描述符,它被认为是一个数据描述符是可读可写的
这样我们不仅可以在改变对象的状态后去做一些事情,而且还能把新的状态存起来了
这样我们只需要在get中去执行对应的操作就可以改变页面上的显示内容了
经过一番改造
现在我们菜单栏按钮点击事件中只需要下面这些操作就可以改变页面的内容
nav_btn.onclick = function (e){
// headerStatus 判断一下我的卡片有没有被打开 如果有则是关闭我的卡片功能
if(headerStatus.mycardIsShow)
{
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
// 因为使用的都是一个关闭按钮所以就不需要据需执行了
// 完成这个关闭我的卡片功能自动退出
return
}
// 改变菜单栏状态
headerStatus.navIsShow = !headerStatus.navIsShow
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
}
左上角小头像点击事件
function mycardSwitchEvent(e){
// 如果菜单栏正在开启状态就关闭菜单栏 但是不去改变关闭按钮的样式
if(headerStatus.navIsShow){
headerStatus.navIsShow = ! headerStatus.navIsShow
}else {
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
}
// 改变我的卡片显示状态
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
}
// 我的卡片按钮点击事件
mycard_switch.onclick = mycardSwitchEvent
下面是核心内容
// 给导航栏状态设置监听的方法
Object.defineProperty(headerStatus, 'navIsShow',{
configurable: true,
enumerable: true, // 说明可以被枚举访问到
// 菜单栏
set: function (value) {
// 当这个navIsShow被重新赋值之后就会进入这个函数
//
// console.log("navIsShow设置了新值",value)
// 执行我们定义好的对导航栏操作函数
navMenuChange(value)
// 把这个值写入带有下划线的属性当中
headerStatus._navIsShow = value
},
get: function (){
// 当重需要读取时从带有下划线的属性中去拿
return headerStatus._navIsShow
}
})
// 这个是我的卡片状态 和上面一毛一样
Object.defineProperty(headerStatus, 'mycardIsShow',{
configurable: true,
enumerable: true,
get: function (){
return headerStatus._mycardIsShow
},
set: function (value){
myCardChange(value)
headerStatus._mycardIsShow = value
}
})
// 这个是按钮显示状态 和上面一毛一样
Object.defineProperty(headerStatus, 'navBtnStaatus', {
configurable: true,
enumerable: true,
get: function (){
return headerStatus._navBtnStaatus
},
set: function (value){
navBtnChange(value)
headerStatus._navBtnStaatus = value
}
})
可以发现现在的set只需要做两件事
- 拿着新值去调用控制DOM的方法
- 存储新的值
下面是几个改变页面状态的函数
function navMenuChange(status){
/***
* 这个是导航菜单状态切换的方法
* 传入的status 如果是真就展开 如果是假的就关闭
* 打开时 右上角按钮必须是关闭按钮
* 关闭时右边的按钮必须是菜单按钮 除非我的卡片在展开状态
*/
if(status){
// 应该立即展开
nav.classList.add("nav-btn-active")
}else {
// 应该立即关闭
nav.classList.remove("nav-btn-active")
}
}
function myCardChange(status){
/**
* 这个是我的卡片状态切换
* 传入status 如果是真则展开
* 如果此时的菜单栏已经打开则改变菜单栏状态
* 我的卡片展示过程中左上角的头像会消失然后失去事件
* 关闭后自动恢复 打开时右上角必须是关闭按钮 关闭后必须就是菜单按钮
*/
if(status){
// 立即展开
mycard.classList.add("my-card-show")
// 头像隐藏
mycard_switch.classList.add("nav-btn-fade-out")
// 头像点击事件清除
mycard_switch.onclick = null
}else {
// 立即关闭
mycard.classList.remove("my-card-show")
mycard_switch.classList.remove("nav-btn-fade-out")
mycard_switch.classList.add("nav-btn-fade-in")
mycard_switch.onclick = mycardSwitchEvent
// headerStatus.navBtnStaatus = !headerStatus.navBtnStaatus
}
}
//
function navBtnChange(status){
/***
* 菜单按钮状态切换
* 传入状态如果是真则显示菜单按钮
* 反之显示关闭按钮
* 按钮执行的操作和我没有半毛线关系我就是个动画函数
*/
// 如果是true表示需要变为x
if(status){
console.log("显示关闭按钮")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-out")
},500)
nav_btn.classList.add("nav-btn-fade-in")
nav_btn.classList.add("icon-guanbi")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}else {
console.log("显示菜单按钮")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("icon-guanbi")
nav_btn.classList.remove("nav-btn-fade-out")
},500)
nav_btn.classList.add("nav-btn-fade-in")
setTimeout(function (){
nav_btn.classList.add("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}
}
因为实现方式不一样 所有需要有一些小小的改动
我们会发现使用这种方式我们只需要在 逻辑部分也就是按钮的点击事件去判断他的状态然后改变 我们不要用复杂的函数调用就可以实现一样的功能而且逻辑清晰
只要状态改变了那么get就会自动的调用操作DOM的方法来完成页面的修改
现在是全部的代码 请忽略写的
和
一样的CSS代码和HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的主页</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="static/font/font.css">
<link rel="stylesheet" href="static/css/clear.css">
<!-- 鸣谢! iconfont 白嫖图标站-->
<link rel="stylesheet" href="//at.alicdn.com/t/font_2174708_s99m5gom8u.css">
<link rel="stylesheet" href="static/font/font.css">
<style>
/*请忽略这一段写的和便便一样的css代码*/
.header{
width: 100%;
height: auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
/*头部*/
}
.header-in{
text-align: center;
/*头部左右文本居中*/
}
/*副标题*/
.header-in .subtitle{
display: block;
text-align: center;
/*副标题*/
}
.header-in .portrait {
display: block;
/*header中的小头像按钮*/
flex-shrink: 0; /*图片不拉伸*/
width: 40px;
height: 40px;
border: none;
margin: 10px 20px;
border-radius: 20px;
overflow: hidden;
}
.header-in .portrait img{
width: 40px;
height: 40px;
display: block;
/*里面的图片 方便起见就搞成了块元素*/
}
.header-title{
display: flex;
height: 60px;
/*标题的盒子*/
}
.header-in .title > h1{
display: block;
height: 35px;
font-size: 20px;
line-height: 35px;
text-align: center;
/*大标题*/
}
.header-in .title .subtitle{
display: block;
height: 25px;
font-size: 12px;
line-height: 25px;
/*副标题*/
}
.header-in .title{
display: block;
height: auto;
width: calc(100% - 60px);
padding-right: 60px;
}
.nav-btn{
/*菜单按钮*/
display: block;
position: absolute;
width: 30px;
height: 30px;
right: 0;
top: 0;
font-size: 30px;
line-height: 30px;
margin: 15px 20px;
border-radius: 5px;
transition: opacity 0.5s;
}
.nav-btn:active{
/*按下去效果*/
background-color: #ddd;
}
.nav-btn-fade-in{
/*按钮淡入效果*/
opacity: 1;
transition: opacity 1s;
}
.nav-btn-fade-out{
/*按钮淡出效果*/
opacity: 0;
transition: opacity 0.5s;
}
.my-card{
/*我的信息卡片*/
opacity: 0;
overflow: hidden;
margin: 0 auto;
box-shadow: none;
max-height: 0;
transition: all 0.5s ease-in-out;
}
.my-card-info{
/*信息盒子*/
margin: 0 auto 0 auto;
height: auto;
}
.my-card-info > .name{
/*我的名字*/
text-align: center;
font-size: 20px;
line-height: 25px;
display: block;
}
.my-card .portrait{
/*卡片中的大头像*/
width: 100px;
height: 100px;
background: cadetblue;
border-radius: 50px;
margin: 0 auto 10px auto;
overflow: hidden;
border: 5px solid #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.25);
}
.my-card .portrait-img{
/*头像图片*/
display: block;
width: 100%;
height: 100%;
margin: 0 auto;
}
/*一下内容自行理解*/
.my-card .info{
margin: 5px auto;
height: 40px;
text-align: center;
}
.my-card .info-item{
display: inline-block;
width: 40px;
height: 40px;
/*background: #333333;*/
}
.my-card .info-item .data{
font-size: 15px;
line-height: 20px;
text-align: center;
}
.my-card .info-item .name{
font-size: 10px;
line-height: 20px;
}
.my-card .info .data{
display: block;
}
.my-card .info .name{
display: block;
}
.my-card .media{
text-align: center;
display: none;
}
.my-card .media-item{
display: inline-block;
height: 25px;
/*background: #ccc;*/
line-height: 25px;
padding: 0 5px;
border-radius: 5px;
}
.my-card-show{
/*个人卡片展开效果*/
opacity: 1;
max-height: 250px;
transition: all 0.5s ease-out;
}
.media-item:hover , .media-item:active{
background: #ddd;
}
/*下面是菜单*/
.nav{
max-height: 0;
overflow: hidden;
transition: max-height 0.5s;
width: 100%;
}
.nav-item{
/*菜单元素*/
width: 100%;
min-height: 50px;
vertical-align: center;
box-sizing: border-box;
padding: 0 12.5px;
}
.nav-item::after{
content: '';
display: block;
width: 50%;
margin: 0 auto;
height: 0;
position: relative;
border-bottom: 1px dashed #999999;
bottom: 0;
}
.nav-item:active{
/*点击效果*/
background-color: #ccc;
}
.nav-btn-active{
/*足够多的高度*/
max-height: 250px;
transition: max-height ease-in-out 0.5s;
}
.nav-item-icon{
/*菜单中标题前面的小圆球*/
width: 15px;
height: 15px;
border: 0.5px dashed #000;
box-sizing: border-box;
display: inline-block;
vertical-align:middle;
border-radius: 12.5px;
text-align: center;
margin: 0 5px;
}
.nav-item-icon-action {
border: none;
background-color: #00cc00;
box-shadow: 0 0 2.5px rgba(0,0,0,0.25);
}
.nav-item-title{
font-family: "等线 Light",serif;
display:inline-block;
line-height: 50px;
vertical-align:middle;
font-size: 20px;
text-align: center;
/*color: #fff;*/
}
.portrait-img{
display: block;
width: 100%;
height: 100%;
margin: 0 auto;
}
</style>
</head>
<body>
<!--头部开始-->
<div class="header">
<!-- 一个容器用来存放标题部分和导航栏部分-->
<div class="header-in">
<!-- 标题栏部分-->
<div class="header-title">
<div id="mycard-switch" class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" alt="my_logo">
</div>
<div class="title">
<h1>标题</h1>
<h2 class="subtitle">
副标题
</h2>
</div>
<div id="nav-btn" class="nav-btn iconfont icon-liebiao3 "></div>
</div>
<!-- 导航栏菜单部分-->
<ul id="nav" class="nav">
<li class="nav-item">
<a href="#">
<i class="nav-item-icon nav-item-icon-action"></i>
<span class="nav-item-title">主页</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon "></i>
<span class="nav-item-title">分类</span>
</a>
</li>
<li class="nav-item nav-item-action">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">文章</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">关于</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">留言</span>
</a>
</li>
</ul>
</div>
<!-- 我的卡片-->
<div id="mycard" class="my-card ">
<div class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" alt="my_logo">
</div>
<div class="my-card-info">
<h1 class="name">
我的名字
</h1>
<ul class="info">
<li class="info-item">
<span class="data">11245</span>
<span class="name">日志</span>
</li>
<li class="info-item">
<span class="data">10</span>
<span class="name">分类</span>
</li>
<li class="info-item">
<span class="data">500</span>
<span class="name">留言</span>
</li>
</ul>
<ul class="media">
<li class="media-item">
<a target="_blank" href="https://space.bilibili.com/125931368">
<i class="iconfont icon-bilibili-line "></i>
<span class="media-name">BILIBILI</span>
</a>
</li>
<li class="media-item">
<a target="_blank" href="https://github.com/PeiJingbobo">
<i class="iconfont icon-github "></i>
<span class="media-name">GitHub</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<script type="text/javascript">
/***
* 简单说明一下要制作的效果
* 导航栏有两个按钮 左边小头像 和右边菜单按钮
* 菜单按钮有两个状态 分别展示菜单按钮和关闭按钮
* 头像分别是隐藏和显示 隐藏时点击没有效果
* 显示时点击展开我的卡片 如果导航栏菜单展开则关闭导航栏菜单
* 点击导航栏菜单按钮展开导航栏菜单
*
* **/
// 所有的内容在windows .onload中 以防dom还没加载完就开始执行了
window.onload = function (){
// 获取一些需要的东西
// 导航栏展开关闭按钮
let nav_btn = document.getElementById("nav-btn");
// 导航栏本栏
let nav = document.getElementById("nav");
// 我的卡片开关按钮(header中的小头像)
let mycard_switch = document.getElementById("mycard-switch")
// 我的卡片本片
let mycard = document.getElementById("mycard")
// 他们的状态信息全部都存在这个对象当中 你只需要改变这个对象的内容就可以自动的改变页面上的元素
// 这就是数据驱动
// 不带下划线的是用来操作存取的 带下划线的是用来存储的
let headerStatus = {
// 存储描述符 用于set 修改当前状态 使用get 和set 操作 _navIsShow, _mycardIsShow, _navBtnStaatus
"navIsShow": false, // 设置默认值都是关闭状态
"mycardIsShow" : false,// 设置默认值都是关闭状态
"navBtnStaatus": true,// 默认显示菜单按钮
// 数据描述符 用于存储当前状态 可以枚举找到 可以被赋值 可以被读取
"_navIsShow": null,
"_mycardIsShow" : null,
"_navBtnStaatus": null,
}
// 菜单按钮点击事件
nav_btn.onclick = function (e){
// headerStatus 判断一下我的卡片有没有被打开 如果有则是关闭我的卡片功能
if(headerStatus.mycardIsShow)
{
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
// 因为使用的都是一个关闭按钮所以就不需要据需执行了
// 完成这个关闭我的卡片功能自动退出
return
}
// 改变菜单栏状态
headerStatus.navIsShow = !headerStatus.navIsShow
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
}
// 为了以后可以轻松地添加删除我的卡片按钮事件所以准备一个函数
function mycardSwitchEvent(e){
// 如果菜单栏正在开启状态就关闭菜单栏 但是不去改变关闭按钮的样式
if(headerStatus.navIsShow){
headerStatus.navIsShow = ! headerStatus.navIsShow
}else {
headerStatus.navBtnStaatus = ! headerStatus.navBtnStaatus
}
// 改变我的卡片显示状态
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
}
// 我的卡片按钮点击事件
mycard_switch.onclick = mycardSwitchEvent
// 给导航栏状态设置监听的方法
Object.defineProperty(headerStatus, 'navIsShow',{
configurable: true,
enumerable: true, // 说明可以被枚举访问到
// 菜单栏
set: function (value) {
// 当这个navIsShow被重新赋值之后就会进入这个函数
//
// console.log("navIsShow设置了新值",value)
// 执行我们定义好的对导航栏操作函数
navMenuChange(value)
// 把这个值写入带有下划线的属性当中
headerStatus._navIsShow = value
},
get: function (){
// 当重需要读取时从带有下划线的属性中去拿
return headerStatus._navIsShow
}
})
// 这个是我的卡片状态 和上面一毛一样
Object.defineProperty(headerStatus, 'mycardIsShow',{
configurable: true,
enumerable: true,
get: function (){
return headerStatus._mycardIsShow
},
set: function (value){
myCardChange(value)
headerStatus._mycardIsShow = value
}
})
// 这个是按钮显示状态 和上面一毛一样
Object.defineProperty(headerStatus, 'navBtnStaatus', {
configurable: true,
enumerable: true,
get: function (){
return headerStatus._navBtnStaatus
},
set: function (value){
navBtnChange(value)
headerStatus._navBtnStaatus = value
}
})
function navMenuChange(status){
/***
* 这个是导航菜单状态切换的方法
* 传入的status 如果是真就展开 如果是假的就关闭
* 打开时 右上角按钮必须是关闭按钮
* 关闭时右边的按钮必须是菜单按钮 除非我的卡片在展开状态
*/
if(status){
// 应该立即展开
nav.classList.add("nav-btn-active")
}else {
// 应该立即关闭
nav.classList.remove("nav-btn-active")
}
}
function myCardChange(status){
/**
* 这个是我的卡片状态切换
* 传入status 如果是真则展开
* 如果此时的菜单栏已经打开则改变菜单栏状态
* 我的卡片展示过程中左上角的头像会消失然后失去事件
* 关闭后自动恢复 打开时右上角必须是关闭按钮 关闭后必须就是菜单按钮
*/
if(status){
// 立即展开
mycard.classList.add("my-card-show")
// 头像隐藏
mycard_switch.classList.add("nav-btn-fade-out")
// 头像点击事件清除
mycard_switch.onclick = null
}else {
// 立即关闭
mycard.classList.remove("my-card-show")
mycard_switch.classList.remove("nav-btn-fade-out")
mycard_switch.classList.add("nav-btn-fade-in")
mycard_switch.onclick = mycardSwitchEvent
// headerStatus.navBtnStaatus = !headerStatus.navBtnStaatus
}
}
//
function navBtnChange(status){
/***
* 菜单按钮状态切换
* 传入状态如果是真则显示菜单按钮
* 反之显示关闭按钮
* 按钮执行的操作和我没有半毛线关系我就是个动画函数
*/
// 如果是true表示需要变为x
if(status){
console.log("显示关闭按钮")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-out")
},500)
nav_btn.classList.add("nav-btn-fade-in")
nav_btn.classList.add("icon-guanbi")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}else {
console.log("显示菜单按钮")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("icon-guanbi")
nav_btn.classList.remove("nav-btn-fade-out")
},500)
nav_btn.classList.add("nav-btn-fade-in")
setTimeout(function (){
nav_btn.classList.add("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}
}
}
</script>
</body>
</html>
传统的折叠动画实现方式
<!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="static/css/clear.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2174708_s99m5gom8u.css">
<link rel="stylesheet" href="static/font/font.css">
<style>
*{
transition: all 0.5s;
}
body{
background-color: #d0e1f2;
display: flex;
justify-content: center;
padding-top: 50px;
/*overflow: hidden;*/
}
.left{
width: 296px;
/*margin: 0 8px;*/
}
.right{
width: 694px;
margin-left: 10px;
}
.header{
width: 100%;
height: auto;
/*background-color: white;*/
border-radius: 5px;
box-sizing: border-box;
transition: all 2s;
/*overflow: hidden;*/
/*padding: 0 20px;*/
}
.header-in{
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
height: auto;
/*width: 80%;*/
/*background-color: #d0e1f2;*/
/*margin: 0 20px;*/
box-sizing: border-box;
/*border-bottom: 1px solid #ccc;*/
/*margin: ;*/
position: relative;
/*height: 70p/x;*/
font-size: 0;
background-color: white;
border-radius: 5px;
}
.header h1,h2{
text-align: center;
}
.title{
padding-top: 10px;
/*font-size: 20px;*/
/*line-height: 30px;*/
/*!*font-weight: 900;*!*/
/*font-family: "Source Code Pro",serif;*/
}
.header-in .title > h1{
display: block;
height: 30px;
/*background-color: #333333;*/
font-size: 20px;
line-height: 30px;
text-align: center;
font-family: "Source Code Pro",serif;
}
.header-in .title .subtitle{
display: block;
height: 30px;
font-size: 12px;
line-height: 30px;
font-family: "Source Code Pro",serif;
}
.my-card{
/*display: none;*/
/*position: relative;*/
/*top: 0;*/
min-height: 250px;
width: 100%;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
padding: 20px 25px;
}
.portrait-img{
display: block;
width: 100%;
height: 100%;
margin: 0 auto;
}
.portrait{
width: 100px;
height: 100px;
background: cadetblue;
border-radius: 50px;
margin: 0 auto 10px auto;
overflow: hidden;
border: 5px solid #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.25);
}
.my-card-info > .name{
text-align: center;
display: block;
font-size: 20px;
height: 30px;
}
.info{
margin: 5px auto;
height: 50px;
/*background: chocolate;*/
text-align: center;
}
.info-item{
display: inline-block;
width: 50px;
height: 50px;
/*background: #333333;*/
}
.info .data{
display: block;
}
.info .name{
display: block;
}
.my-card .media{
text-align: center;
}
.my-card .media-item{
display: inline-block;
height: 25px;
/*background: #ccc;*/
line-height: 25px;
padding: 0 5px;
border-radius: 5px;
}
.media-item:hover , .media-item:active{
background: #ddd;
}
.nav{
/*position: relative;*/
width: 100%;
height: 250px;
/*background-color: cadetblue;*/
overflow: hidden;
border-radius: 0 0 5px 5px;
transition: all 1s;
background-color: white;
margin:0 auto 30px auto;
}
.nav-item{
width: 100%;
min-height: 50px;
vertical-align: center;
box-sizing: border-box;
padding: 0 12.5px;
}
.nav-item:active ,.nav-item:hover{
background-color: #ddd;
}
.nav-item-icon{
width: 15px;
height: 15px;
border: 0.5px dashed #000;
box-sizing: border-box;
/*background-color: #d0e1f2;*/
display: inline-block;
line-height: 50px;
vertical-align:middle;
border-radius: 12.5px;
text-align: center;
/*margin: 0 auto;*/
margin: 0 5px;
}
.nav-item-icon-action {
border: none;
background-color: #00cc00;
box-shadow: 0 0 2.5px rgba(0,0,0,0.25);
/*box-sizing:;*/
}
.nav-item-title{
font-family: "等线 Light",serif;
display:inline-block;
line-height: 50px;
vertical-align:middle;
font-size: 20px;
text-align: center;
/*color: #fff;*/
}
.nav-btn{
display: none;
position: absolute;
/*display: block;*/
width: 30px;
height: 30px;
right: 0px;
top: 0;
font-size: 30px;
line-height: 30px;
margin: 15px 20px;
border-radius: 5px;
/*background-color: #333333;*/
}
.nav-btn:active{
background-color: #eee;
}
.nav-btn-active{
height: 250px;
transition: height 1s;
}
/*@keyframes fade-in {*/
/* from {*/
/* opacity: 0;*/
/* !*display: none;*!*/
/* } to{*/
/* opacity: 1;*/
/* !*display: block;*!*/
/* }*/
/*}*/
/*@keyframes fade-out {*/
/* from {*/
/* opacity: 1;*/
/* !*display: block;*!*/
/* }to{*/
/* opacity: 0;*/
/* !*display: none;*!*/
/* }*/
/*}*/
.nav-btn-fade-in{
opacity: 1;
transition: opacity 0.5s;
/*animation: fade-out 0.5s;*/
/*transform: ;*/
}
.nav-btn-fade-out{
opacity: 0;
transition: opacity 0.5s;
/*animation: fade-out 0.5s;*/
}
.content{
min-height: 1000px;
width: 694px;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
/*background-color: saddlebrown;*/
}
.footer{
margin: 10px auto;
width: 694px;
min-height: 100px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
}
.nav-show{
transition: nav-show 1s;
}
.nav-close{
transition: nav-close 1s;
}
.header-in .portrait{
display: none;
}
@media screen and (max-width:960px){
body{
padding-top:0;
flex-direction: column;
}
.left{
width: 100%;
}
.right{
width: 100%;
margin:0 auto;
}
.header{
position: absolute;
width: 100%;
/*box-shadow: none;*/
padding: 0;
/*height: 60px;*/
box-shadow: 0 2px 5px rgba(0,0,0,0.25);
}
.header h1,h2{
text-align: center;
}
.header-in{
box-shadow: none;
border-radius: 0;
border: none;
}
.header-title{
display: flex;
height: 60px;
}
.header-in .portrait{
display: block;
margin: 10px 15px ;
}
.nav-btn{
display: block;
}
.header-in .title{
display: block;
padding: 0;
height: auto;
}
.header-in .subtitle{
display: block;
}
.header-in .title{
/*width: 100%;*/
width: calc(100% - 60px);
padding-right: 60px;
}
.my-card{
overflow: hidden;
padding: 0;
margin: 0 auto;
min-height: 0;
border-radius: 0;
box-shadow: none;
height: 0;
transition: height 1s;
}
.portrait-img{
display: block;
width: 100%;
height: 100%;
margin: 0 auto;
}
.header-in .portrait img{
width: 40px;
height: 40px;
display: block;
}
.header-in .portrait {
flex-shrink: 0;
width: 40px;
height: 40px;
/*margin: 0 20px;*/
border: none;
margin: 10px 20px;
}
/*overflow: hidden;*/
/*border: 2px solid #fff;*/
/*box-shadow: 0 0 2px rgba(0,0,0,0.25);*/
.my-card-info-show{
}
.my-card-info{
margin: 0 auto 0 auto;
height: max-content;
/*display: none;*/
}
.my-card-info > .name{
text-align: center;
font-size: 20px;
line-height: 25px;
display: block;
}
.info{
margin: 5px auto;
height: 40px;
/*background: chocolate;*/
text-align: center;
}
.info-item{
display: inline-block;
width: 40px;
height: 40px;
/*background: #333333;*/
}
.info-item .data{
font-size: 15px;
line-height: 20px;
text-align: center;
}
.info-item .name{
font-size: 10px;
line-height: 20px;
}
.info .data{
display: block;
}
.info .name{
display: block;
}
.my-card .media{
text-align: center;
display: none;
}
.my-card .media-item{
display: inline-block;
height: 25px;
/*background: #ccc;*/
line-height: 25px;
padding: 0 5px;
border-radius: 5px;
}
.my-card-show{
height: 200px;
transition: height 1s;
}
.media-item:hover , .media-item:active{
background: #ddd;
}
.nav{
width: 100%;
height: 0;
/*min-height: 50px;*/
/*background-color: cadetblue;*/
overflow: hidden;
border-radius: 0 0 5px 5px;
/*height: 0;*/
margin: 0 auto;
/*transform: all 1s;*/
transition: all 1s;
}
.nav-item{
width: 100%;
min-height: 50px;
vertical-align: center;
box-sizing: border-box;
padding: 0 12.5px;
}
.nav-item:hover{
/*background-color: green;*/
}
.nav-item:active{
background-color: green;
}
.nav-btn-active{
height: 250px;
}
.content{
/*display: none;*/
width: 100%;
margin-top: 60px;
border-radius: 0;
box-shadow: none;
}
.footer{
width: 100%;
}
.nav-item:hover,.media-item:hover {
background: none;
}
.nav-item:active{
background-color: green;
}
.media-item:hover{
background-color: #dddddd;
}
/*.my-card*/
}
</style>
</head>
<body>
<!-- <div class="body">-->
<div class="left">
<div class="header">
<div class="header-in">
<div class="header-title">
<div id="mycard-switch" class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" >
</div>
<div class="title">
<h1> Pei Jingbo</h1>
<h2 class="subtitle">
副标题
</h2>
</div>
<div id="nav-btn" class="nav-btn iconfont icon-liebiao3 "></div>
</div>
<ul id="nav" class="nav">
<li class="nav-item">
<a href="#">
<i class="nav-item-icon nav-item-icon-action"></i>
<span class="nav-item-title">主页</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon "></i>
<span class="nav-item-title">分类</span>
</a>
</li>
<li class="nav-item nav-item-action">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">文章</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">关于</span>
</a>
</li>
<li class="nav-item">
<a href="#">
<i class="nav-item-icon icon-home"></i>
<span class="nav-item-title">留言</span>
</a>
</li>
</ul>
</div>
<div id="mycard" class="my-card ">
<div class="portrait">
<img src="static/img/portrait.jpg" class="portrait-img" >
</div>
<div class="my-card-info">
<h1 class="name">
PeiJingbo
</h1>
<ul class="info">
<li class="info-item">
<span class="data">11245</span>
<span class="name">日志</span>
</li>
<li class="info-item">
<span class="data">10</span>
<span class="name">分类</span>
</li>
<li class="info-item">
<span class="data">500</span>
<span class="name">留言</span>
</li>
</ul>
<ul class="media">
<li class="media-item">
<a target="_blank" href="https://space.bilibili.com/125931368">
<i class="iconfont icon-bilibili-line "></i>
<span class="media-name">BILIBILI</span>
</a>
</li>
<li class="media-item">
<a target="_blank" href="https://github.com/PeiJingbobo">
<i class="iconfont icon-github "></i>
<span class="media-name">GitHub</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="right">
<div class="content">
</div>
<footer class="footer">
</footer>
</div>
<!-- </div>-->
<script type="text/javascript">
// 导航栏展开关闭按钮
let nav_btn = document.getElementById("nav-btn");
// 导航栏本栏
let nav = document.getElementById("nav");
// 我的卡片开关按钮(header中的小头像)
let mycard_switch = document.getElementById("mycard-switch")
// 我的卡片本片
let mycard = document.getElementById("mycard")
let headerStatus = {
"navIsShow": false,
"mycardIsShow" : false,
"navBtnStaatus": true,
}
nav_btn.onclick = function (e){
if(headerStatus.mycardIsShow)
{
myCardChange()
return
}
navMenuChange()
}
function mycardSwitchEvent(e){
myCardChange()
// 如果菜单正在打开则关闭
if(headerStatus.navIsShow){
navMenuChange()
}
}
mycard_switch.onclick = mycardSwitchEvent
function navMenuChange(){
if(headerStatus.navIsShow){
// 说明现在展开着呢 应该立即关闭菜单
nav.classList.remove("nav-btn-active")
headerStatus.navIsShow = !headerStatus.navIsShow
if(!headerStatus.mycardIsShow){
navBtnChange()
}
}else {
// 应该立即展开
nav.classList.add("nav-btn-active")
headerStatus.navIsShow = !headerStatus.navIsShow
navBtnChange()
}
}
function myCardChange(){
if(headerStatus.mycardIsShow){
// 立即关闭
mycard.classList.remove("my-card-show")
mycard_switch.classList.remove("nav-btn-fade-out")
mycard_switch.classList.add("nav-btn-fade-in")
mycard_switch.onclick = mycardSwitchEvent
// 手动修改按钮样式
navBtnChange()
}else{
// 立即展开
mycard.classList.add("my-card-show")
// 头像隐藏
mycard_switch.classList.add("nav-btn-fade-out")
mycard_switch.onclick = null
// 虽然是重复的语句但是不能放在外面 这样浏览器执行的顺序就不一样 就不能再onclick中判断这个值了
headerStatus.mycardIsShow = !headerStatus.mycardIsShow
if(!headerStatus.navBtnStaatus){
return
}
navBtnChange()
}
}
function navBtnChange(){
if(headerStatus.navBtnStaatus){
console.log("显示关闭按钮")
nav_btn.classList.add("nav-btn-fade-out")
nav_btn.classList.remove("icon-liebiao3")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-out")
nav_btn.classList.add("nav-btn-fade-in")
},500)
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-in")
nav_btn.classList.add("icon-guanbi")
},500)
}else {
console.log("显示菜单按钮")
nav_btn.classList.remove("icon-guanbi")
nav_btn.classList.add("nav-btn-fade-out")
setTimeout(function (){
nav_btn.classList.remove("nav-btn-fade-out")
nav_btn.classList.add("nav-btn-fade-in")
},500)
setTimeout(function (){
nav_btn.classList.add("icon-liebiao3")
nav_btn.classList.remove("nav-btn-fade-in")
},500)
}
headerStatus.navBtnStaatus = !headerStatus.navBtnStaatus
}
</script>
</body>
</html>
本文地址:https://blog.csdn.net/qq_25911173/article/details/109568624