iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 24
1
Modern Web

新手也能懂的JS30系列 第 24

JS30-Day24-Sticky Nav

Day24-課題內容

進入JS30的倒數第七天,今天將透過捲動事件加上 CSS屬性的更動,讓我們的導覽列在即將滾出畫面的時候,可以固定在畫面最上方,而不會消失在頁面當中。
實作範例

進入課題

在第十三天的課題Slide in on scroll中,我們有介紹到 window.scrollXwindow.scrollY這兩個代表目前頁面 X軸與 Y軸捲動量的屬性,element.offsetWidthelement.offsetHeight 代表該元素的寬與高的屬性。而一開始可以看到目前的頁面配置與 HTML 結構如下:

<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">
    <p>Lorem ipsum dol....</p>
    <p>Lorem ipsum dol....</p>
    <p>Lorem ipsum dol....</p>
    .....
</div>

我們的導覽列上方有一塊含有背景圖示的 <header> 元素,再來是我們要製作特效的 <nav> 元素,最後則是 <div> 元素容器裏頭放了很多 <p> 元素字串。
要讓頁面在滾動的時候產生特效,首先我們要先加上監聽事件 onscroll 及觸發的函示,這邊也先將我們會用到的元素選取起來:

window.addEventListener('scroll', stick);
const navbar = document.querySelector('#main');
const header = document.querySelector('header');
const wrap = document.querySelector('.site-wrap');
const logo = document.querySelector('.logo');

在執行的函式當中,我們要讓導覽列碰到頁面上方邊界時,就讓他一直維持在該位置,也就是下圖的情形:

當下的狀態為上方 header 元素已經都被往上捲動到看不見了。換成判斷條件的話,也就是為 window.scrollY 等於 header 元素的高度,因此在我們函式中的判斷條件可以寫成如下,當Y軸方向捲動的量大於該高度,就執行讓 nav 元素黏在固定位置的程式碼:

function stick(event) {
    const condition = header.offsetHeight;
    if (window.scrollY >= condition) {
    ...
    } else {
    ...
    }
};

要讓畫面中的 DOM 元素,固定在目前頁面中可視區域的位置,我們就要使用到 position:fixed 這個 CSS 屬性。[2]
含有固定定位(position: fixed)CSS 屬性的元素,會相對於瀏覽器視窗來定位,這代表就頁面捲動時,它還是會固定在頁面上相同的位置。而固定定位的元素不會保留它原本在頁面應有的空間,也不會跟其他元素的配置互相干擾。
而當我們把 nav 元素由原本的 position:relative 改成 position:fixed 之後,他在原本的空間大小也會消失,下方的 <div> 元素位置也因此被往上移動。所以在這同時我們需要將 <div> 元素位置往下移動 nav 元素的高度,才不會出現瞬間移動的神奇事件。而這邊我們選用的方法是修改 body 的 padding top 屬性,讓所有在 body 中的元素被往下移動一定距離:

function stick (event) {
    const condition = header.offsetHeight;
    if (window.scrollY >= condition) {
        navbar.style.position = 'fixed';
        document.body.style.paddingTop = navbar.offsetHeight+'px';
    } else {
        navbar.style.position = 'relative';
        document.body.style.paddingTop = 0;
    }
  };

最後還要加上一些特效。在作者的範例當中,當導覽列移動到頁面上方的時候,原本藏起來的 logo 會跑出來,但是移開的時候他又會消失不見,這邊作者是利用 max-width 這個 CSS 屬性來操作這個特效。透過 max-width 屬性,我們可以指定元素的最大寬度,可以做出更加完美的響應式網頁。
而作者在一開始的 CSS 屬性中,就將該元素的 max-width 這個 CSS 屬性設為0,並且加上 overflow:hidden,讓裡頭的內容無法顯示出來,如此一來就成功做出他消失不見的效果,而當導覽列移動到我們指定的位置之後,再將 max-width 加上一寬度值,就能讓裡頭的內容顯示出來。
作者在 CSS 屬性中將整個 <nav> 容器指定為 display:flex ,並讓裡頭每個 li 都使用了 flex:1 屬性,所以當 logo 出來的時候,我們只要設定一個上限數值,讓他的寬度可以隨著 flex-grow 延伸即可,另外作者也在裡頭加了一些其他特效,大家可以都自己嘗試看看。完整的程式碼如下:

function stick (event) {
    //取得 header 元素的高度
    const condition = header.offsetHeight;
    //當捲動高度大於header 元素的高度 執行下列程式碼
    if (window.scrollY >= condition) {
        //將 nav 元素的 position 屬性修改為 fixed
        navbar.style.position = 'fixed';
        //將 body 加上 paddingTop 將內容物往下移動   
        document.body.style.paddingTop = navbar.offsetHeight+'px';
        //透過將 logo 元素的最大寬度由0變成500 讓內容物出現在頁面上
        logo.style.maxWidth = `500px`;
        //讓下方的 div 容器有放大的效果                           
        wrap.style.transform = `scale(1)`;
    } else {
      navbar.style.position = 'relative';
      document.body.style.paddingTop = 0;
      wrap.style.transform = `scale(0.98)`;
      logo.style.maxWidth = 0;
    }
 };

總結

今天透過判斷頁面捲動的值,結合許多不同屬性的 CSS 屬性,打造出響應式互動設計的頁面,讓使用者在使用的時候,可以更加便利以及充滿驚喜。大家可以在完成今天的課題之後,嘗試加上更多不同的 CSS屬性,讓整個頁面看起來更加好玩喔!

參考資料

  1. javascript30
  2. 學習 CSS 版面配置-關於 position 屬性

上一篇
JS30-Day23-Speech Synthesis
下一篇
JS25-Event Capture, Propagation, Bubling & Once
系列文
新手也能懂的JS3030

尚未有邦友留言

立即登入留言