iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
自我挑戰組

JS30 學習日記系列 第 24

Day 24 - Sticky Nav

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

今天我們要做的是置頂導覽列,當使用者拉下捲軸而視窗頂部碰到導覽列頂部時,若往後繼續下拉捲軸則將導覽列始終固定在最上方。


解析程式碼

HTML 部分

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>

JS 部分

宣告常數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

那為什麼我們還要特別設定bodypadding-top呢? 因為在導覽列(#main)的 CSS 設定中,我們使用了position: fixed;,這樣就會造成導覽列(#main)原本佔的空間突然空出來,之後下方的.site-wrap看上面還有空間就會擠上來,但擠上來的速度過快,因此在視覺上就會出現詭異的彈跳。

為避免這種狀況發生,我們可以在置頂導覽列的同時,指定bodypadding-top: nav.ofsetHeight;,讓原本導覽列佔有的空間,被padding-top補上。不置頂導覽列的同時,理所當然我們就要將bodypadding-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);

CSS 部分

下面是在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

範例網頁請按此


上一篇
Day 23 - Speech Synthesis
下一篇
Day 25 - Event Capture, Propagation, Bubbling and Once
系列文
JS30 學習日記30

尚未有邦友留言

立即登入留言