Kolejny ciekawy temat, który musimy przerobić, aby dobrze poznać frontend i JavaScript. Do dzieła.

Ok, będziemy analizować kod z poniższym markupem (przykład z githuba):

<body>
    
    <nav>
        <ul>
            <li class="active">
                <a href="#home">Home</a>
            </li>
            <li>
                <a href="#about-me">About me</a>
            </li>
            <li>
                <a href="#my-projects">My projects</a>
            </li>
            <li>
                <a href="#testimonials">Testimonials</a>
            </li>
            <li>
                <a href="#contact">Contact</a>
            </li>
        </ul>
    </nav>

To jest część nav, teraz część druga:

<main>
        <section id="home">
            <h1>Home</h1>
            <p>
                Lorem (...)
            </p>
        </section>

        <section id="about-me">
            <h1>About Me</h1>
           <p>
                Lorem (...)
            </p>
        </section>

        <section id="my-projects">
            <h1>My projects</h1>
            <p>
                Lorem (...)
            </p>
        </section>

        <section id="testimonials">
            <h1>Testimonials</h1>
            <p>
                Lorem (...)
            </p>
        </section>

        <section id="contact">
            <h1>Contact</h1>
            <p>
                Lorem (...)
            </p>
        </section>
    </main>

    <script src="script.js"></script>

Mamy też jakieś CSSy, nas interesuje właściwość scroll-behavior:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    scroll-behavior: smooth;
}

Reszta to bardzo prosty flex-box, gdzie na lewo mamy navbar w starym stylu (guzik pod guzikiem), na prawo reszta strony:

body {
    display: flex;
    flex-wrap: wrap;
    font-family: Verdana, Geneva, Tahoma, sans-serif;
}

body nav {
    display: block;
    width: 150px;
}

body nav ul {
    position: fixed;
    margin: 0;
    padding: 0;
    width: 150px;
    height: 100vh;
    list-style: none;
    background: #17653a;
}

body nav ul li {
    transition: 0.2s;
}

body nav ul li.active {
    background: #f44336;
}

body nav ul li.active a {
    color: white;
}

body nav ul li a {
    display: inline-block;
    width: 100%;
    padding: 5px 10px;
    color: white;
    text-decoration: none;
}

body main {
    width: 100%;
    margin-left: 150px;
    text-align: justify;
}

body main section {
    margin: 50px;
    line-height: 30px;
}

body main section h1 {
    text-align: center;
    margin-bottom: 10px;
}

Tu nie ma co rozkminiać, aby poznać te CSSy to inne przykłady robiliśmy, tutaj przeklejamy, bo chcemy nie tracąc czasu poznać coś innego.

Ok, script.js:

window.addEventListener("scroll", (event) => {
    let menuItems = document.querySelectorAll("nav li");
    let sections = document.querySelectorAll("main section");

    sections.forEach( (section, i) => {
        let top = section.getBoundingClientRect().top;

        if (top < window.innerHeight) {
            menuItems.forEach( i => i.classList.remove("active") );
            menuItems[i].classList.add("active");
        }

    } );
} );

O co chodzi – wyjaśniam:

  • ilość sekcji i ilość przycisków jest jednakowa
  • scroll event, odpalany, gdy currentTarget jest scrollowany (jest nim okno)
  • zakładam, że nie propaguje i nie bąbelkuje, ale mniejsza o to, mam tylko nadzieję, że eventy jeszcze pamiętamy
  • chcemy sprawdzać scroll sekcji, zaś klasy dodawać i odejmować nav-itemom
  • getboundingclientrect znamy, top to odległość od góry (można zamiast top dać y)
  • dalej sprawdzamy, czy element został przescrollowany i nadajemy/odejmujemy odpowiednie klasy

Dodam, że my się tak bawić nie będziemy, bo od takich rzeczy są intersection observery. Ale w ramach nauki i te metody musieliśmy poznać.

Dobra, teraz event scrollend, sam go sobie dopisałem:

window.addEventListener("scrollend", (event) => {
    alert("scrollend" + window.scrollY)
  });

Swoją drogą limitowana dostępność + słabo na razie działa. Odpala się po dojechaniu do końca jak i dojechaniu do początku. Można to poprawić:

window.addEventListener("scrollend", (event) => {
    if(window.scrollY < 100)
        return;
    alert("scrollend" + window.scrollY)
  });

Teraz działa jako-tako poprawnie…

Ok, wykonajmy sobie jakiś scroll do góry, po potwierdzeniu:

window.addEventListener("scrollend", (event) => {
    if(window.scrollY < 100)
        return;
    let scrollToTop = confirm("Scroll to top?");
    if(scrollToTop)
        window.scroll(window.scrollX, 0);
  });

Działa, choć zepsuliśmy event scroll, teraz active jest zablokowany na ostatnim elemencie, JSowe przeskrolowanie się nie zarejestrowało jako event…

To może tak:

window.addEventListener("scrollend", (event) => {
    if(window.scrollY < 100)
        return;
    let scrollToTop = confirm("Scroll to top?");
    if(scrollToTop)
        document.querySelector("main").scrollIntoView({behavior: 'smooth'});
  });

Też nie bardzo, cóż, musimy poznać intersection observera, ale dobrze, abyśmy te bardziej klasyczne metody pracy z JS też poznali…