JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks
、No Compilers
、No Libraries
、No Boilerplate
在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。
另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。
今天我們要做的是置頂導覽列,當使用者拉下捲軸而視窗頂部碰到導覽列頂部時,若往後繼續下拉捲軸則將導覽列始終固定在最上方。
header
是網頁上方的標題。
#main
是網頁的導覽列,包含.logo
(初始隱藏)還有其他五個項目。
.site-wrap
放的是一大堆假文還有圖片。
<header>
<h1>A story about getting lost.</h1>
</header>
<nav id="main">
<ul>
<li class="logo"><a href="#">LOST.</a></li>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Images</a></li>
<li><a href="#">Locations</a></li>
<li><a href="#">Maps</a></li>
</ul>
</nav>
<div class="site-wrap">
<!--以下是假文還有圖片,為不佔空間所以省略-->
</div>
宣告常數nav
取得網頁導覽列。
宣告常數topOfNav
取得導覽列頂部離視窗最上方的距離。
const nav = document.querySelector('#main');
const topOfNav = nav.offsetTop;
我們置頂導覽列的機制主要是透過觀測捲軸的"捲動量"來決定是否置頂捲軸。為此首先要在window
註冊scroll event listener
在捲軸捲動時不斷觸發,然後用fixNav()
進行事件處理。
在fixedNav()
裡面,我們可以判斷現在視窗的 y 方向捲動量(window.scrollY
)是否超過導覽列的頂部(topOfNav
),如果超過的話就在body
上添加fixed-nav
這個 class,反之若沒有超過則移除fixed-nav
。
那為什麼我們還要特別設定body
的padding-top
呢? 因為在導覽列(#main
)的 CSS 設定中,我們使用了position: fixed;
,這樣就會造成導覽列(#main
)原本佔的空間突然空出來,之後下方的.site-wrap
看上面還有空間就會擠上來,但擠上來的速度過快,因此在視覺上就會出現詭異的彈跳。
為避免這種狀況發生,我們可以在置頂導覽列的同時,指定body
的padding-top: nav.ofsetHeight;
,讓原本導覽列佔有的空間,被padding-top
補上。不置頂導覽列的同時,理所當然我們就要將body
的padding-top
改回 0。
function fixNav(){
console.log(topOfNav,window.scrollY);
if(window.scrollY >= topOfNav){
document.body.style.paddingTop = nav.offsetHeight + 'px';
document.body.classList.add('fixed-nav');
}else{
document.body.style.paddingTop = 0;
document.body.classList.remove('fixed-nav');
}
}
window.addEventListener('scroll',fixNav);
下面是在body
上有.fixed-nav
這個 class 時的特殊設定。
設定導覽列position: fixed
,在預設上是固定在left: 0;
、top: 0;
的位置(視窗左上角),然後加上一點陰影。
.fixed-nav nav{
position: fixed;
box-shadow: 0 5px rgba(0,0,0,0.1);
}
把原本在導覽列隱藏的logo
顯示出來,設定max-width
(最大寬度)為500px,這裡原本也可以用width: 500px;
就好,但是設定width
會讓transition
產生的動畫效果失效。
.fixed-nav li.logo{
max-width: 500px; /*use width can't show animation effect*/
}
在置頂導覽列的同時,把下方的文章區塊略為放大。
.fixed-nav .site-wrap{
transform: scale(1);
}
HTMLElement.offsetTop
HTMLElement.offsetHeight
Element.classList
CSS Transform
Element: scroll event
Window.scrollY