原生JS實現全屏切換以及導航欄滑動隱藏及顯示——重構前


2017-1-15更新:原生JS實現全屏切換以及導航欄滑動隱藏及顯示——修改,這篇文章中的代碼解決了bug。

思路分析:

  1. 向后滾動鼠標滾輪,頁面向下全屏切換;向前滾動滾輪,頁面向上全屏切換。切換過程為動畫效果。
  2. 第一屏時,導航欄固定在頁面頂部,切換到第二屏時,導航條向左滑動隱藏。切換回第一屏時,導航欄向右滑動顯示。
  3. 頁面顯示的不是第一平時,當鼠標指針滑動到頁面的頭部區域,導航欄向右滑出;鼠標指針移出頭部區域時,導航欄向左滑動隱藏。
  4. 當視口尺寸小於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             &lt;
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>
View Code

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    }
View Code

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 };
View Code

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我暫時還不知道怎么解決,不過與事件沖突比起來,還不是特別嚴重,也不影響使用。

暫時就想到這么多,接下來對代碼進行重構,以便后期好維護。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM