TABLE OF CONTENTS

Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

Scrolling Nav là hiệu ứng cho Nav khi chúng ta scroll xuống content tiếp theo thì trên thanh Nav sẽ add thêm animation vào tạo hiệu ứng đẹp mắt 😁.

Thông thường chúng ta sử dụng Scrolling Nav cho one page. Tức các section như About, Services, Contact sẽ ở trong một trang và khi chúng ta scroll, tới section nào thì tương ứng trên thanh Nav sẽ thêm animation.

Chúng ta sẽ cùng thực hành cách tạo Scrolling Nav nhé 🔥.

DEMO

Đầu tiên các bạn hãy coi DEMO mình đã làm sẵn dưới đây:

Như DEMO các bạn có thể thấy khi mình scroll tới section nào thì trên thanh Navigation sẽ được đánh dấu để ta biết đang ở content đó.

Thực hành

Mình sẽ thêm các đoạn HTML chính sau đây để giải thích cho các bạn ⚡.

Đầu tiên chúng ta sẽ tạo một thanh Nav, href là # + ví trị muốn scroll đến.

<ul class="nav">
  <li class="nav-item">
    <a class="nav-link" href="#about">About</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#services">Services</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#contact">Contact</a>
  </li>
</ul>

Lưu ý: khi thanh Nav các bạn để là fixed sẽ gặp trường hợp khi click vào nội dung muốn scroll đến sẽ bị thanh Nav đè lên. Mình đã có cách xử lí cho trường hợp này, các bạn đọc tại đây.

Sau đó ta tạo một section chứa id để click vào Nav sẽ tự scroll đến section này. Ví dụ ta tạo một section có id là services:

<section id="services" class="section">
  <div class="container">
    <h2>Services we offer</h2>
    <p class="lead">
      Lorem ipsum dolor sit amet, consectetur adipisicing elit.
      Aut optio velit inventore, expedita quo laboriosam possimus
      ea consequatur vitae, doloribus consequuntur ex. Nemo
      assumenda laborum vel, labore ut velit dignissimos.
    </p>
  </div>
</section>

Mình đã tạo sẵn HTML và kết quả như DEMO trên 😄👉

Tiếp theo ta đến phần JavaScript:

Đầu tiên ta sẽ lấy tất các các thẻ a chứa href đến các section và các section chứa id.

const navItems = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('.section');

Sau khi lấy xong, ta thêm event scroll cho window:

window.addEventListener(
    'scroll',
    function () {
        // ... code here
    },
    { passive: true }
);

Mình truyền passive: true để cải thiện performance cho scroll. Tìm hiểu thêm tại đây

Tiếp theo ta cần xử lí: Lấy được vị trí của section và id của nó. Để lấy được vị trí của section so với top mình dùng offsetTop.

window.addEventListener(
    'scroll',
    function () {
        // window.pageYOffset, pageYOffset only use for window
        sections.forEach((section) => {
            // 50 is height of nav
            const top = section.offsetTop - 50;            
        });
    },
    { passive: true }
);

Mỗi các section sẽ có vị trí khác nhau, top = section.offsetTop - 50 sẽ là vị trí của section. Lí do mình bỏ đi 50px là do thanh Nav có height là 50px, Ta sẽ bỏ height của thanh Nav đi.

Tiếp theo ta sẽ lấy href của nav-item và id của section:

window.addEventListener(
    'scroll',
    function () {
        // window.pageYOffset, pageYOffset only use for window
        sections.forEach((section) => {
            // 50 is height of nav
            const top = section.offsetTop - 50;

            navItems.forEach((navItem) => {
                const hrefNav = navItem.href.match(/#[a-zA-Z]+/)[0];
                const idSection = '#' + section.id;
            });
        });
    },
    { passive: true }
);

Khi dùng navItem.href ta sẽ nhận được toàn bộ đường dẫn như sau:

http://127.0.0.1:5500/scrollNav/index.html#about. Vì mình chỉ muốn lấy #about cho giống với id đã đặt cho section nên ta sẽ dùng match() và truyền regular expression vào. Kết quả trả về trong trường hợp trên là một mảng các chuỗi đã match.

Tiếp theo ta sẽ so sánh chuỗi lấy được với chuỗi id của section.

window.addEventListener(
    'scroll',
    function () {
        // window.pageYOffset, pageYOffset only use for window
        sections.forEach((section) => {
            // 50 is height of nav
            const top = section.offsetTop - 50;

            navItems.forEach((navItem) => {
                const hrefNav = navItem.href.match(/#[a-zA-Z]+/)[0];
                const idSection = '#' + section.id;

                hrefNav === idSection
                      ? navItem.parentElement.classList.add('active')
                      : navItem.parentElement.classList.remove('active');

            });
        });
    },
    { passive: true }
);

Thêm điều kiện nếu ta scroll tới vị trí của section ta sẽ thêm animation:

window.addEventListener(
    'scroll',
    function () {
        // window.pageYOffset, pageYOffset only use for window
        sections.forEach((section) => {
            // 50 is height of nav
            const top = section.offsetTop - 50;
            const html = document.documentElement;
            navItems.forEach((navItem) => {
                const hrefNav = navItem.href.match(/#[a-zA-Z]+/)[0];
                const idSection = '#' + section.id;

                if (html.scrollTop >= top) {
                    hrefNav === idSection
                        ? navItem.parentElement.classList.add('active')
                        : navItem.parentElement.classList.remove('active');
                }
            });
        });
    },
    { passive: true }
);

Tới đây ta gần thành công ! 🔥.Tuy nhiên điều kiện trên chỉ thêm class active khi scroll tới vị trí section, thế còn điều kiện để remove class ví dụ ta scroll về đầu trang không phải vị trí của mỗi section trên kia thì sao 🧐.

Ta chỉ cần thêm các điều kiện sau để hoàn thành Scrolling Nav 👇:

const top = section.offsetTop - 50;
const html = document.documentElement;
const height = section.offsetHeight;

if (html.scrollTop >= top && top + height >= html.scrollTop) {
  hrefNav === idSection
    ? navItem.parentElement.classList.add('active')
  : navItem.parentElement.classList.remove('active');
} else {
  hrefNav === idSection &&
    navItem.parentElement.classList.remove('active');
}

Mình sẽ giải thích điều kiện trên:

html.scrollTop >= top && top + height >= html.scrollTop: Điều kiện này xảy ra khi ta scroll tới vị trí của section và lúc này vị trí scroll vẫn ở nội dung section chưa vượt ra ngoài.

Nếu scroll vượt quá top + height thì có nghĩa là vị trí scroll hiện tại đã vượt khỏi nội dung của section.

Ngược lại tức là ta đã rời khỏi vị trí section => Ta sẽ remove class active của Nav item vừa add class active. nếu thắc mắc các bạn hay bỏ đoạn hrefNav === idSection rồi scroll sẽ hiểu ^^.

Đây là code hoàn chỉnh của chúng ta:

const navItems = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('.section');

window.addEventListener(
    'scroll',
    function () {
        // window.pageYOffset, pageYOffset only use for window
        sections.forEach((section) => {
            // 50 is height of nav
            const top = section.offsetTop - 50;
            const html = document.documentElement;
            const height = section.offsetHeight;

            navItems.forEach((navItem) => {
                const hrefNav = navItem.href.match(/#[a-zA-Z]+/)[0];
                const idSection = '#' + section.id;

                if (html.scrollTop >= top && top + height >= html.scrollTop) {
                    hrefNav === idSection
                        ? navItem.parentElement.classList.add('active')
                        : navItem.parentElement.classList.remove('active');
                } else {
                    hrefNav === idSection &&
                        navItem.parentElement.classList.remove('active');
                }
            });
        });
    },
    { passive: true }
);

Đây là kết quả ^^:

Kết luận

Như vậy là chúng ta đã hoàn thành xong Scrolling Nav. Khá hay ho đúng không nào 😄. Nếu có thắc mắc gì các bạn có thể liên hệ mình nhé 🔥.

Ngày mới tốt lành 🔥🙌.

Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.