原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前
2017-1-15更新:原生JS实现全屏切换以及导航栏滑动隐藏及显示——修改,这篇文章中的代码解决了bug。
思路分析:
- 向后滚动鼠标滚轮,页面向下全屏切换;向前滚动滚轮,页面向上全屏切换。切换过程为动画效果。
- 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏。切换回第一屏时,导航栏向右滑动显示。
- 页面显示的不是第一平时,当鼠标指针滑动到页面的头部区域,导航栏向右滑出;鼠标指针移出头部区域时,导航栏向左滑动隐藏。
- 当视口尺寸小于768px时,切换页面不隐藏导航条,但是导航条的项目要隐藏,通过点击按钮来显示和隐藏项目。
本篇代码是重构前的代码。
HTML代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>全屏滚动</title> 8 9 <style type="text/css"> 10 </style> 11 </head> 12 <body> 13 <header id="nav-head"> 14 <nav id="nav"> 15 <div id="navbar-header"> 16 <div id="logo-box"> 17 <!--<img id="logo-brand" src="乔巴.jpg" />--> 18 Fogwind 19 </div> 20 <button id="navbar-toggle" type="button"> 21 <span class="icon-bar"></span> 22 <span class="icon-bar"></span> 23 <span class="icon-bar"></span> 24 </button> 25 </div> 26 <!-- 27 <button id="navbar-slip" type="button"> 28 < 29 </button> 30 --> 31 <ul id="navbar-item" class="navbar-item-block navbar-item-none"> 32 <hr id="navbar-item-border" /> 33 <li class="navbar-list"> 34 <a href="http://www.battlenet.com.cn/zh/">战网1</a> 35 </li> 36 <li class="navbar-list"> 37 <a href="http://www.battlenet.com.cn/zh/">战网2</a> 38 </li> 39 <li class="navbar-list"> 40 <a href="http://www.battlenet.com.cn/zh/">战网3</a> 41 </li> 42 <li class="navbar-list"> 43 <a href="http://www.battlenet.com.cn/zh/">战网4</a> 44 </li> 45 </ul> 46 47 48 </nav> 49 </header> 50 51 <!--全屏滚动--> 52 <div id="full-page"> 53 <div id="page-box"> 54 <div id="page-one" class="page"></div> 55 <div id="page-two" class="page"></div> 56 <div id="page-three" class="page"></div> 57 <div id="page-four" class="page"></div> 58 </div> 59 </div> 60 <script> 61 </script> 62 </body> 63 </html>
CSS代码:
1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 html,body{ 6 height: 100%; 7 } 8 a{ 9 text-decoration: none; 10 } 11 /********************导航栏样式**********************/ 12 #nav-head{ 13 height: 50px; 14 width: 100%; 15 position: absolute; 16 background: transparent; 17 z-index: 900; 18 } 19 #nav{ 20 background-color: #222; 21 height: auto; 22 position: fixed; 23 width: 100%; 24 top: 0; 25 z-index: 1000; 26 } 27 28 #logo-box{ 29 float: left; 30 height: 50px; 31 padding: 15px 15px; 32 margin-left: -15px; 33 font-size: 18px; 34 line-height: 20px; 35 color: #9d9d9d; 36 } 37 /*当log-brand是图片时*/ 38 #logo-brand{ 39 display: block; 40 max-height: 35px; 41 } 42 /*导航栏右边按钮中的三道杠*/ 43 .icon-bar{ 44 display: block; 45 width: 22px; 46 height: 2px; 47 border-radius: 1px; 48 background-color: #fff; 49 } 50 #navbar-toggle .icon-bar + .icon-bar{ 51 margin-top: 4px; 52 } 53 /*导航栏列表ul*/ 54 #navbar-item{ 55 list-style: none; 56 } 57 #navbar-item-border{/*ul上部分割线,大屏时不显示*/ 58 border: 0; 59 height: 1px; 60 background-color: #333; 61 } 62 .navbar-item-block{ 63 display: block; 64 overflow: hidden; 65 } 66 67 .navbar-list a{ 68 display: block; 69 color: #9d9d9d; 70 padding: 15px 15px; 71 line-height: 20px; 72 } 73 .navbar-list a:hover{ 74 color: #fff; 75 } 76 /*向左隐藏导航栏按钮 77 #navbar-slip { 78 float: right; 79 }*/ 80 /*大屏时*/ 81 @media (min-width: 768px) { 82 #navbar-header{ 83 float: left; 84 height: 50px; 85 padding: 0 15px; 86 } 87 #navbar-item-border{ 88 display: none; 89 } 90 .navbar-list{ 91 float: left; 92 } 93 94 #navbar-toggle{ 95 display: none; 96 } 97 } 98 /*中小屏时*/ 99 @media (max-width: 767px) { 100 #navbar-header{ 101 display: block; 102 overflow: hidden; 103 height: 50px; 104 padding: 0 15px; 105 } 106 #navbar-toggle{ 107 float: right; 108 background-color: transparent; 109 border: 1px solid #333; 110 border-radius: 4px; 111 padding: 9px 10px; 112 margin-top: 8px; 113 margin-bottom: 8px; 114 } 115 #navbar-toggle:hover{ 116 background-color: #333; 117 } 118 119 .navbar-item-none{ 120 display: none; 121 } 122 123 /*#navbar-slip{ 124 display: none; 125 }*/ 126 } 127 128 /*************************全屏滚动样式**************************/ 129 #full-page{ 130 height: 100%; 131 position: relative; 132 overflow: hidden; 133 } 134 #page-box{ 135 height: 100%; 136 width: 100%; 137 position: absolute; 138 } 139 .page{ 140 height: 100%; 141 } 142 #page-one{ 143 background-color: #6495ED; 144 } 145 #page-two{ 146 background-color: #B8860B; 147 } 148 #page-three{ 149 background-color: #8470FF; 150 } 151 #page-four{ 152 background-color: #D87093; 153 }
CSS代码参考了Bootstrap的代码。
这其中最关键的是html,body{height: 100%},这条样式可以初始化body的高度为视口高度,即使它里面没有内容。媒体查询规定了小屏幕下的样式。导航栏用了固定定位,全屏切换的每个页面用div包裹,这个div绝对定位,通过控制其top属性实现全屏切换。
JS代码:
1 var bool = true;//存储导航栏的状态,显示时为true,隐藏时为false 2 3 //跨浏览器的添加事件的函数 4 function addHandler(element, type, handler) { 5 if(element.addEventListener) { 6 element.addEventListener(type, handler, false); 7 } else if(element.attachEvent) { 8 element.attachEvent('on' + type, handler); 9 } else { 10 element['on' + type] = handler; 11 } 12 } 13 14 //跨浏览器的添加mousewheel事件的函数 15 function addMouseWheelEvent(element,func) { 16 17 if(typeof element.onmousewheel == "object") { 18 19 addHandler(element,"mousewheel",func); 20 } 21 if(typeof element.onmousewheel == "undefined") { 22 //alert(1); 23 //兼容Firefox 24 addHandler(element,"DOMMouseScroll",func); 25 } 26 } 27 /**********中小屏显示/隐藏导航栏中项目的代码***********/ 28 var navbarbtn = document.getElementById("navbar-toggle"); 29 //保存navbarbtn被点击了几次 30 navbarbtn.count = 0; 31 navbarbtn.onclick = function() { 32 var navbaritem = document.getElementById("navbar-item"); 33 if(navbarbtn.count === 0) { 34 //第一次点击时显示项目 35 navbaritem.className = "navbar-item-block"; 36 navbarbtn.count++; 37 } else { 38 //第二次点击时隐藏项目,并重置navbarbtn.count 39 navbaritem.className = "navbar-item-none navbar-item-block"; 40 navbarbtn.count = 0; 41 } 42 43 }; 44 45 /*************向左隐藏导航条,向右显示导航条****************/ 46 var nav = document.getElementById('nav'); 47 //分别用来保存导航栏开始滑动和结束滑动的时间 48 //利用两者差值来判断动画效果是否完成 49 nav.startDate = 0; 50 nav.stopDate = 0; 51 //动画效果完成所需的时间 52 nav.t = 300; 53 54 //向左隐藏 55 function navSlideLeft() { 56 if(nav.navmove) { 57 clearInterval(nav.navmove); 58 } 59 60 //获取nav的计算样式表 61 var computedStyle; 62 if(document.defaultView.getComputedStyle) { //DOM 2标准方法 63 computedStyle = document.defaultView.getComputedStyle(nav,null); 64 } else { 65 computedStyle = nav.currentStyle;//兼容IE方法 66 } 67 68 var width = parseInt(computedStyle.width), speed = width/(nav.t/10), left = parseInt(computedStyle.left); 69 //IE中computedStyle.left为auto 70 //下面的if语句用来兼容IE 71 if(!Boolean(left)) { 72 left = 0; 73 } 74 //如果nav没有向左隐藏,执行向左隐藏代码 75 //alert(width); 76 if(left > -width) { 77 78 nav.startDate = new Date(); 79 nav.navmove = setInterval(function() { 80 nav.stopDate = new Date(); 81 if(nav.stopDate - nav.startDate < nav.t) { 82 left += -speed; 83 //nav.style.left += left + 'px'; 84 } else { 85 left = -width; 86 //nav.style.left = left + 'px'; 87 clearInterval(nav.navmove); 88 } 89 nav.style.left = left + 'px'; 90 },10); 91 } else { 92 return; 93 } 94 } 95 96 function navSlideRight() { 97 if(nav.navmove) { 98 clearInterval(nav.navmove); 99 } 100 //获取nav的计算样式表 101 var computedStyle; 102 if(document.defaultView.getComputedStyle) { //DOM 2标准方法 103 computedStyle = document.defaultView.getComputedStyle(nav,null); 104 } else { 105 computedStyle = nav.currentStyle;//兼容IE方法 106 } 107 108 var width = parseInt(computedStyle.width), speed = width/(nav.t/10), left = parseInt(computedStyle.left); 109 110 //如果nav没有向左隐藏,执行向左隐藏代码 111 if(left < 0) { 112 113 nav.startDate = new Date(); 114 nav.navmove = setInterval(function() { 115 nav.stopDate = new Date(); 116 if(nav.stopDate - nav.startDate < nav.t) { 117 left += speed; 118 //nav.style.left += left + 'px'; 119 } else { 120 left = 0; 121 //nav.style.left = left + 'px'; 122 clearInterval(nav.navmove); 123 } 124 nav.style.left = left + 'px'; 125 },10); 126 } else { 127 return; 128 } 129 } 130 131 /*全屏滚动代码*/ 132 var pageBox = document.getElementById('page-box'); 133 if(document.defaultView.getComputedStyle) { //DOM 2标准方法 134 pageBox.computedStyle = document.defaultView.getComputedStyle(pageBox,null); 135 } else { 136 pageBox.computedStyle = pageBox.currentStyle;//兼容IE方法 137 } 138 pageBox.startDate = 0; 139 pageBox.stopDate = 0; 140 pageBox.t = 300; 141 142 //获取有几屏 143 pageBox.pageChildren = pageBox.getElementsByTagName('div').length; 144 145 //切换计数 146 pageBox.num = 1; 147 148 //超时调用ID,优化mousewheel事件,防止连续触发 149 pageBox.mousewheelTimer = null; 150 151 function pageSlideUp(num) { 152 if(pageBox.pageScroll) { 153 clearInterval(pageBox.pageScroll); 154 } 155 var height = parseInt(pageBox.computedStyle.height); 156 var top = parseInt(pageBox.computedStyle.top); 157 var speed = height/(pageBox.t/10); 158 pageBox.startDate = new Date(); 159 pageBox.pageScroll = setInterval(function() { 160 pageBox.stopDate = new Date(); 161 if(pageBox.stopDate - pageBox.startDate < pageBox.t) { 162 top += -speed; 163 } else { 164 top = -height*num; 165 clearInterval(pageBox.pageScroll); 166 } 167 pageBox.style.top = top + "px"; 168 },10); 169 } 170 171 function pageSlideDown(num) { 172 if(pageBox.pageScroll) { 173 clearInterval(pageBox.pageScroll); 174 } 175 var height = parseInt(pageBox.computedStyle.height); 176 var top = parseInt(pageBox.computedStyle.top); 177 var speed = height/(pageBox.t/10); 178 pageBox.startDate = new Date(); 179 pageBox.pageScroll = setInterval(function() { 180 pageBox.stopDate = new Date(); 181 if(pageBox.stopDate - pageBox.startDate < pageBox.t) { 182 top += speed; 183 } else { 184 top = -height*num; 185 clearInterval(pageBox.pageScroll); 186 } 187 pageBox.style.top = top + "px"; 188 },10); 189 } 190 191 192 function mouseWheelListener(event) { 193 194 event = event || window.event; 195 //获取滚动方向 196 var wheelDelta; 197 if(event.wheelDelta) { 198 wheelDelta = event.wheelDelta; 199 } else { 200 201 wheelDelta = -event.detail;//兼容Firefox 202 } 203 //alert(wheelDelta); 204 //通过超时调用优化滚动事件 205 if(pageBox.mousewheelTimer) { 206 clearTimeout(pageBox.mousewheelTimer); 207 } 208 //当连续两次滚动鼠标滚轮的时间间隔小于pageBox.t时,不触发滚动效果 209 if((pageBox.stopDate - pageBox.startDate > 0) && (pageBox.stopDate - pageBox.startDate < pageBox.t)) { 210 //console.log(pageBox.stopDate - pageBox.startDate); 211 return; 212 } 213 //mousewheel事件与mouseover事件的时间间隔小于nav.t时,不触发事件,防止事件冲突。 214 var nowtime = new Date(); 215 if(nowtime - navhead.leaveDate<nav.t) { 216 return; 217 218 } 219 220 //当滚轮向后滚动时 221 if(wheelDelta < 0) { 222 if(pageBox.num <= pageBox.pageChildren - 1) { 223 pageBox.mousewheelTimer = setTimeout(pageSlideUp(pageBox.num),20); 224 pageBox.num++;//最后等于pageBox.pageChildren,这里为4 225 //console.log(pageBox.num); 226 } else { 227 return; 228 } 229 } else {//当滚轮向前滚动时 230 if(pageBox.num <= pageBox.pageChildren && pageBox.num > 1) { 231 pageBox.num--; 232 pageBox.mousewheelTimer = setTimeout(pageSlideDown(pageBox.num-1),20); 233 //console.log(pageBox.num); 234 } else { 235 pageBox.num = 1; 236 return; 237 } 238 } 239 240 //隐藏导航条 241 /* 242 if(parseInt(pageBox.computedStyle.width) > 768 && event.clientY > 50) { 243 if(pageBox.num != 1 && bool) 244 245 navSlideLeft(); 246 bool = false; 247 } 248 if(pageBox.num == 1 && !bool) { 249 navSlideRight(); 250 bool = true; 251 } */ 252 253 //解决导航条进出切换bug,主要是两次事件触发的时间间隔小于动画时间所致 254 //因为动画效果由三个事件触发:mousewheel,navhead的mouseover和pageBox的mouseover,事件之间有冲突 255 //包括代码中的所有时间间隔的判断都是为了解决此bug 256 //导航栏高度固定为50px 257 if(parseInt(pageBox.computedStyle.width) > 768 && event.clientY > 50) { 258 if(pageBox.num == 2 && bool) { 259 navSlideLeft(); 260 bool = false; 261 } 262 if(pageBox.num == 1 && !bool) { 263 navSlideRight(); 264 bool = true; 265 } 266 } 267 } 268 269 //给document添加鼠标滚动事件 270 addMouseWheelEvent(document,mouseWheelListener); 271 272 //保存超时调用ID,优化resize事件,防止连续触发 273 var resizeTimer = null; 274 //视口宽度小于768时,导航条不隐藏,大于768时才隐藏 275 //同时保证全屏切换时,每一屏的高度始终等于视口高度 276 window.onresize = function() { 277 if (resizeTimer) { 278 clearTimeout(resizeTimer) 279 } 280 resizeTimer = setTimeout(function() { 281 pageBox.style.top = (-parseInt(pageBox.computedStyle.height)*(pageBox.num-1)) + "px"; 282 283 if(parseInt(pageBox.computedStyle.width) < 768) { 284 nav.style.left = '0px'; 285 } 286 if(parseInt(pageBox.computedStyle.width) >= 768 && pageBox.num != 1) { 287 //这里有点小bug,最小化再最大化,鼠标滑过头部区域后导航条不消失 288 nav.style.left = (-parseInt(pageBox.computedStyle.width)) + 'px'; 289 bool = false; 290 } 291 292 293 },20); 294 }; 295 296 var navhead = document.getElementById('nav-head'); 297 navhead.overDate = 0; 298 navhead.leaveDate = 0; 299 navhead.onmouseover = function(event) { 300 event = event || window.event; 301 event.target = event.srcElement || event.target; 302 303 //防止navhead的子元素触发事件(也可以阻止事件冒泡) 304 if(event.target.nodeName != this.nodeName) {//换成判断id 305 return; 306 } 307 if(pageBox.num == 1 || parseInt(pageBox.computedStyle.width) < 768 ) { 308 return; 309 } 310 311 navhead.overDate = new Date(); 312 //console.log(navhead.overDate - navhead.leaveDate); 313 //这里的时间间隔判断会产生一个bug 314 //当切换到下一屏时,如果指针足够快划入头部区域,导航条不出现,要滑出来等至少0.3s,才行 315 //如果你足够快,让指针在头部与页面之间来回切换,导航条始终不出现。 316 //下面pageBox的mouseover事件同理 317 if((navhead.overDate - navhead.leaveDate) > pageBox.t) { 318 if(!bool) { 319 navSlideRight(); 320 bool = true; 321 } 322 } 323 /** 324 325 326 327 //console.log(navhead.overDate - navhead.leaveDate); 328 if((navhead.overDate - navhead.leaveDate) > pageBox.t) { 329 if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num != 1) { 330 if(parseInt(nav.style.left) < 0) { 331 navSlideRight(); 332 } 333 334 335 } 336 }**/ 337 }; 338 339 pageBox.onmouseover = function(event) { 340 if(pageBox.num == 1 || parseInt(pageBox.computedStyle.width) < 768 ) { 341 return; 342 } 343 //console.log(123); 344 event = event || window.event; 345 navhead.leaveDate = new Date(); 346 //console.log(navhead.leaveDate.getTime()); 347 if((navhead.leaveDate - navhead.overDate) > pageBox.t) { 348 if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num != 1) { 349 if(bool) { 350 navSlideLeft(); 351 bool = false; 352 } 353 } 354 if(parseInt(pageBox.computedStyle.width) > 768 && pageBox.num == 1) { 355 if(!bool) { 356 navSlideRight(); 357 bool = true; 358 } 359 } 360 } 361 };
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我暂时还不知道怎么解决,不过与事件冲突比起来,还不是特别严重,也不影响使用。
暂时就想到这么多,接下来对代码进行重构,以便后期好维护。