原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前

2023-07-29,,

2017-1-15更新:原生JS实现全屏切换以及导航栏滑动隐藏及显示——修改,这篇文章中的代码解决了bug。

思路分析:

    向后滚动鼠标滚轮,页面向下全屏切换;向前滚动滚轮,页面向上全屏切换。切换过程为动画效果。
    第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏。切换回第一屏时,导航栏向右滑动显示。
    页面显示的不是第一平时,当鼠标指针滑动到页面的头部区域,导航栏向右滑出;鼠标指针移出头部区域时,导航栏向左滑动隐藏。
    当视口尺寸小于768px时,切换页面不隐藏导航条,但是导航条的项目要隐藏,通过点击按钮来显示和隐藏项目。

本篇代码是重构前的代码。

HTML代码:

 <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>全屏滚动</title> <style type="text/css">
</style>
</head>
<body>
<header id="nav-head">
<nav id="nav">
<div id="navbar-header">
<div id="logo-box">
<!--<img id="logo-brand" src="乔巴.jpg" />-->
Fogwind
</div>
<button id="navbar-toggle" type="button">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<!--
<button id="navbar-slip" type="button">
&lt;
</button>
-->
<ul id="navbar-item" class="navbar-item-block navbar-item-none">
<hr id="navbar-item-border" />
<li class="navbar-list">
<a href="http://www.battlenet.com.cn/zh/">战网1</a>
</li>
<li class="navbar-list">
<a href="http://www.battlenet.com.cn/zh/">战网2</a>
</li>
<li class="navbar-list">
<a href="http://www.battlenet.com.cn/zh/">战网3</a>
</li>
<li class="navbar-list">
<a href="http://www.battlenet.com.cn/zh/">战网4</a>
</li>
</ul> </nav>
</header> <!--全屏滚动-->
<div id="full-page">
<div id="page-box">
<div id="page-one" class="page"></div>
<div id="page-two" class="page"></div>
<div id="page-three" class="page"></div>
<div id="page-four" class="page"></div>
</div>
</div>
<script>
</script>
</body>
</html>

CSS代码:

 *{
margin:;
padding:;
}
html,body{
height: 100%;
}
a{
text-decoration: none;
}
/********************导航栏样式**********************/
#nav-head{
height: 50px;
width: 100%;
position: absolute;
background: transparent;
z-index:;
}
#nav{
background-color: #222;
height: auto;
position: fixed;
width: 100%;
top:;
z-index:;
} #logo-box{
float: left;
height: 50px;
padding: 15px 15px;
margin-left: -15px;
font-size: 18px;
line-height: 20px;
color: #9d9d9d;
}
/*当log-brand是图片时*/
#logo-brand{
display: block;
max-height: 35px;
}
/*导航栏右边按钮中的三道杠*/
.icon-bar{
display: block;
width: 22px;
height: 2px;
border-radius: 1px;
background-color: #fff;
}
#navbar-toggle .icon-bar + .icon-bar{
margin-top: 4px;
}
/*导航栏列表ul*/
#navbar-item{
list-style: none;
}
#navbar-item-border{/*ul上部分割线,大屏时不显示*/
border:;
height: 1px;
background-color: #333;
}
.navbar-item-block{
display: block;
overflow: hidden;
} .navbar-list a{
display: block;
color: #9d9d9d;
padding: 15px 15px;
line-height: 20px;
}
.navbar-list a:hover{
color: #fff;
}
/*向左隐藏导航栏按钮
#navbar-slip {
float: right;
}*/
/*大屏时*/
@media (min-width: 768px) {
#navbar-header{
float: left;
height: 50px;
padding: 0 15px;
}
#navbar-item-border{
display: none;
}
.navbar-list{
float: left;
} #navbar-toggle{
display: none;
}
}
/*中小屏时*/
@media (max-width: 767px) {
#navbar-header{
display: block;
overflow: hidden;
height: 50px;
padding: 0 15px;
}
#navbar-toggle{
float: right;
background-color: transparent;
border: 1px solid #333;
border-radius: 4px;
padding: 9px 10px;
margin-top: 8px;
margin-bottom: 8px;
}
#navbar-toggle:hover{
background-color: #333;
} .navbar-item-none{
display: none;
} /*#navbar-slip{
display: none;
}*/
} /*************************全屏滚动样式**************************/
#full-page{
height: 100%;
position: relative;
overflow: hidden;
}
#page-box{
height: 100%;
width: 100%;
position: absolute;
}
.page{
height: 100%;
}
#page-one{
background-color: #6495ED;
}
#page-two{
background-color: #B8860B;
}
#page-three{
background-color: #8470FF;
}
#page-four{
background-color: #D87093;
}

CSS代码参考了Bootstrap的代码。

这其中最关键的是html,body{height: 100%},这条样式可以初始化body的高度为视口高度,即使它里面没有内容。媒体查询规定了小屏幕下的样式。导航栏用了固定定位,全屏切换的每个页面用div包裹,这个div绝对定位,通过控制其top属性实现全屏切换。

JS代码:

   var bool = true;//存储导航栏的状态,显示时为true,隐藏时为false

   //跨浏览器的添加事件的函数
function addHandler(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else if(element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
} //跨浏览器的添加mousewheel事件的函数
function addMouseWheelEvent(element,func) { if(typeof element.onmousewheel == "object") { addHandler(element,"mousewheel",func);
}
if(typeof element.onmousewheel == "undefined") {
//alert(1);
//兼容Firefox
addHandler(element,"DOMMouseScroll",func);
}
}
/**********中小屏显示/隐藏导航栏中项目的代码***********/
var navbarbtn = document.getElementById("navbar-toggle");
//保存navbarbtn被点击了几次
navbarbtn.count = 0;
navbarbtn.onclick = function() {
var navbaritem = document.getElementById("navbar-item");
if(navbarbtn.count === 0) {
//第一次点击时显示项目
navbaritem.className = "navbar-item-block";
navbarbtn.count++;
} else {
//第二次点击时隐藏项目,并重置navbarbtn.count
navbaritem.className = "navbar-item-none navbar-item-block";
navbarbtn.count = 0;
} }; /*************向左隐藏导航条,向右显示导航条****************/
var nav = document.getElementById('nav');
//分别用来保存导航栏开始滑动和结束滑动的时间
//利用两者差值来判断动画效果是否完成
nav.startDate = 0;
nav.stopDate = 0;
//动画效果完成所需的时间
nav.t = 300; //向左隐藏
function navSlideLeft() {
if(nav.navmove) {
clearInterval(nav.navmove);
} //获取nav的计算样式表
var computedStyle;
if(document.defaultView.getComputedStyle) { //DOM 2标准方法
computedStyle = document.defaultView.getComputedStyle(nav,null);
} else {
computedStyle = nav.currentStyle;//兼容IE方法
} var width = parseInt(computedStyle.width), speed = width/(nav.t/10), left = parseInt(computedStyle.left);
//IE中computedStyle.left为auto
//下面的if语句用来兼容IE
if(!Boolean(left)) {
left = 0;
}
//如果nav没有向左隐藏,执行向左隐藏代码
//alert(width);
if(left > -width) { nav.startDate = new Date();
nav.navmove = setInterval(function() {
nav.stopDate = new Date();
if(nav.stopDate - nav.startDate < nav.t) {
left += -speed;
//nav.style.left += left + 'px';
} else {
left = -width;
//nav.style.left = left + 'px';
clearInterval(nav.navmove);
}
nav.style.left = left + 'px';
},10);
} else {
return;
}
} function navSlideRight() {
if(nav.navmove) {
clearInterval(nav.navmove);
}
//获取nav的计算样式表
var computedStyle;
if(document.defaultView.getComputedStyle) { //DOM 2标准方法
computedStyle = document.defaultView.getComputedStyle(nav,null);
} else {
computedStyle = nav.currentStyle;//兼容IE方法
} var width = parseInt(computedStyle.width), speed = width/(nav.t/10), left = parseInt(computedStyle.left); //如果nav没有向左隐藏,执行向左隐藏代码
if(left < 0) { nav.startDate = new Date();
nav.navmove = setInterval(function() {
nav.stopDate = new Date();
if(nav.stopDate - nav.startDate < nav.t) {
left += speed;
//nav.style.left += left + 'px';
} else {
left = 0;
//nav.style.left = left + 'px';
clearInterval(nav.navmove);
}
nav.style.left = left + 'px';
},10);
} else {
return;
}
} /*全屏滚动代码*/
var pageBox = document.getElementById('page-box');
if(document.defaultView.getComputedStyle) { //DOM 2标准方法
pageBox.computedStyle = document.defaultView.getComputedStyle(pageBox,null);
} else {
pageBox.computedStyle = pageBox.currentStyle;//兼容IE方法
}
pageBox.startDate = 0;
pageBox.stopDate = 0;
pageBox.t = 300; //获取有几屏
pageBox.pageChildren = pageBox.getElementsByTagName('div').length; //切换计数
pageBox.num = 1; //超时调用ID,优化mousewheel事件,防止连续触发
pageBox.mousewheelTimer = null; function pageSlideUp(num) {
if(pageBox.pageScroll) {
clearInterval(pageBox.pageScroll);
}
var height = parseInt(pageBox.computedStyle.height);
var top = parseInt(pageBox.computedStyle.top);
var speed = height/(pageBox.t/10);
pageBox.startDate = new Date();
pageBox.pageScroll = setInterval(function() {
pageBox.stopDate = new Date();
if(pageBox.stopDate - pageBox.startDate < pageBox.t) {
top += -speed;
} else {
top = -height*num;
clearInterval(pageBox.pageScroll);
}
pageBox.style.top = top + "px";
},10);
} function pageSlideDown(num) {
if(pageBox.pageScroll) {
clearInterval(pageBox.pageScroll);
}
var height = parseInt(pageBox.computedStyle.height);
var top = parseInt(pageBox.computedStyle.top);
var speed = height/(pageBox.t/10);
pageBox.startDate = new Date();
pageBox.pageScroll = setInterval(function() {
pageBox.stopDate = new Date();
if(pageBox.stopDate - pageBox.startDate < pageBox.t) {
top += speed;
} else {
top = -height*num;
clearInterval(pageBox.pageScroll);
}
pageBox.style.top = top + "px";
},10);
} function mouseWheelListener(event) { event = event || window.event;
//获取滚动方向
var wheelDelta;
if(event.wheelDelta) {
wheelDelta = event.wheelDelta;
} else { wheelDelta = -event.detail;//兼容Firefox
}
//alert(wheelDelta);
//通过超时调用优化滚动事件
if(pageBox.mousewheelTimer) {
clearTimeout(pageBox.mousewheelTimer);
}
//当连续两次滚动鼠标滚轮的时间间隔小于pageBox.t时,不触发滚动效果
if((pageBox.stopDate - pageBox.startDate > 0) && (pageBox.stopDate - pageBox.startDate < pageBox.t)) {
//console.log(pageBox.stopDate - pageBox.startDate);
return;
}
//mousewheel事件与mouseover事件的时间间隔小于nav.t时,不触发事件,防止事件冲突。
var nowtime = new Date();
if(nowtime - navhead.leaveDate<nav.t) {
return; } //当滚轮向后滚动时
if(wheelDelta < 0) {
if(pageBox.num <= pageBox.pageChildren - 1) {
pageBox.mousewheelTimer = setTimeout(pageSlideUp(pageBox.num),20);
pageBox.num++;//最后等于pageBox.pageChildren,这里为4
//console.log(pageBox.num);
} else {
return;
}
} else {//当滚轮向前滚动时
if(pageBox.num <= pageBox.pageChildren && pageBox.num > 1) {
pageBox.num--;
pageBox.mousewheelTimer = setTimeout(pageSlideDown(pageBox.num-1),20);
//console.log(pageBox.num);
} else {
pageBox.num = 1;
return;
}
} //隐藏导航条
/*
if(parseInt(pageBox.computedStyle.width) > 768 && event.clientY > 50) {
if(pageBox.num != 1 && bool) navSlideLeft();
bool = false;
}
if(pageBox.num == 1 && !bool) {
navSlideRight();
bool = true;
} */ //解决导航条进出切换bug,主要是两次事件触发的时间间隔小于动画时间所致
//因为动画效果由三个事件触发:mousewheel,navhead的mouseover和pageBox的mouseover,事件之间有冲突
//包括代码中的所有时间间隔的判断都是为了解决此bug
//导航栏高度固定为50px
if(parseInt(pageBox.computedStyle.width) > 768 && event.clientY > 50) {
if(pageBox.num == 2 && bool) {
navSlideLeft();
bool = false;
}
if(pageBox.num == 1 && !bool) {
navSlideRight();
bool = true;
}
}
} //给document添加鼠标滚动事件
addMouseWheelEvent(document,mouseWheelListener); //保存超时调用ID,优化resize事件,防止连续触发
var resizeTimer = null;
//视口宽度小于768时,导航条不隐藏,大于768时才隐藏
//同时保证全屏切换时,每一屏的高度始终等于视口高度
window.onresize = function() {
if (resizeTimer) {
clearTimeout(resizeTimer)
}
resizeTimer = setTimeout(function() {
pageBox.style.top = (-parseInt(pageBox.computedStyle.height)*(pageBox.num-1)) + "px"; if(parseInt(pageBox.computedStyle.width) < 768) {
nav.style.left = '0px';
}
if(parseInt(pageBox.computedStyle.width) >= 768 && pageBox.num != 1) {
//这里有点小bug,最小化再最大化,鼠标滑过头部区域后导航条不消失
nav.style.left = (-parseInt(pageBox.computedStyle.width)) + 'px';
bool = false;
} },20);
}; var navhead = document.getElementById('nav-head');
navhead.overDate = 0;
navhead.leaveDate = 0;
navhead.onmouseover = function(event) {
event = event || window.event;
event.target = event.srcElement || event.target; //防止navhead的子元素触发事件(也可以阻止事件冒泡)
if(event.target.nodeName != this.nodeName) {//换成判断id
return;
}
if(pageBox.num == 1 || parseInt(pageBox.computedStyle.width) < 768 ) {
return;
} navhead.overDate = new Date();
//console.log(navhead.overDate - navhead.leaveDate);
//这里的时间间隔判断会产生一个bug
//当切换到下一屏时,如果指针足够快划入头部区域,导航条不出现,要滑出来等至少0.3s,才行
//如果你足够快,让指针在头部与页面之间来回切换,导航条始终不出现。
//下面pageBox的mouseover事件同理
if((navhead.overDate - navhead.leaveDate) > pageBox.t) {
if(!bool) {
navSlideRight();
bool = true;
}
}
/** //console.log(navhead.overDate - navhead.leaveDate);
if((navhead.overDate - navhead.leaveDate) > pageBox.t) {
if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num != 1) {
if(parseInt(nav.style.left) < 0) {
navSlideRight();
} }
}**/
}; pageBox.onmouseover = function(event) {
if(pageBox.num == 1 || parseInt(pageBox.computedStyle.width) < 768 ) {
return;
}
//console.log(123);
event = event || window.event;
navhead.leaveDate = new Date();
//console.log(navhead.leaveDate.getTime());
if((navhead.leaveDate - navhead.overDate) > pageBox.t) {
if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num != 1) {
if(bool) {
navSlideLeft();
bool = false;
}
}
if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num == 1) {
if(!bool) {
navSlideRight();
bool = true;
}
}
}
};

JS代码比较乱,后期还要用面向对象思想重构。这里主要说说写的过程中遇到的一些问题及bug。

1、以前写轮播图时,首先想到的都是用位置判断动画效果有没有完成,这容易导致轮播效果不稳定。比如走着走着速度越来越快、图片不是整张显示而是跨张显示或者图片不见了等等,这些bug的产生与Javascript的定时机制有关。所以这次直接以前一次动画函数与当前动画函数的调用时间差来判断动画效果是否完成。

2、鼠标滚动事件在不同浏览器之间的兼容性。参考我以前的文章:mousewheel事件的兼容方法。

3、获取元素计算样式表的方法,主要是跨浏览器的兼容。这里碰到一个问题,一个元素的CSS属性有很多,获取到的计算样式表中各个属性的值在不同浏览器中差别很大,比如我没有明确设置绝对定位的<nav>元素的left属性,在获取到的计算样式表中,Chrome浏览器中left属性为0px,Firefox和IE中则为auto,需要初始化为0px。所以在以后的项目中,如果要获取元素的计算样式表,一定要注意浏览器之间的差异。

4、通过超时调用优化mousewheel事件和resize事件,防止连续触发。

5、如果使用了超时调用或间歇调用一定注意记得清除,特别是实现动画效果的时候。

6、在这个项目中导航条的显示/隐藏有三个事件可以触发,为了解决事件之间的冲突,使用了各个事件之间的触发时间差与动画完成所需时间做判断条件,等动画效果完成才能触发事件,否则不触发。这虽然解决了事件冲突的bug,也带来了一些其他的小bug。比如,先最小化再最大化视口,鼠标滑过头部区域后导航条不消失;当切换到下一屏时,如果指针足够快划入头部区域,导航条不出现,要滑出来等至少0.3s,才可以(其实这两个bug都是同一个原因导致的,就是两次事件的触发时间差小于动画完成所需的时间)。这些bug我暂时还不知道怎么解决,不过与事件冲突比起来,还不是特别严重,也不影响使用。

暂时就想到这么多,接下来对代码进行重构,以便后期好维护。

原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前的相关教程结束。

《原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前.doc》

下载本文的Word格式文档,以方便收藏与打印。