Wheel Event 실습

이성훈·2024년 8월 2일

Javascript

목록 보기
10/11
post-thumbnail

실습

마우스 휠을 내리면 섹션 하나 내려가고, 올리면 섹션 하나 올라가도록 구현

내가 짠 코드

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>scroll</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html {
        scroll-behavior: smooth;
        overflow: hidden;
      }
      .top_menu {
        position: fixed;
        top: 1rem;
        right: 1rem;
        display: flex;
        gap: 1rem;
        transform: translateY(calc(-100% - 1rem));
        transition: transform 0.8s;
      }
      .top_menu a {
        padding: 1rem;
        text-decoration: none;
        color: white;
        background-color: black;
      }
      .top_menu.on {
        transform: translateY(0);
      }
      .top_menu a:hover,
      .top_menu a:active,
      .top_menu a.on {
        background-color: white !important;
        color: black;
      }
      .goTop {
        position: fixed;
        right: 100px;
        bottom: 100px;
        background-color: antiquewhite;
        padding: 1rem;
      }
      .sec_con section {
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      #sec1 {
        background-color: tomato;
      }
      #sec2 {
        background-color: teal;
      }
      #sec3 {
        background-color: antiquewhite;
      }
      #sec4 {
        background-color: aliceblue;
      }
      #sec5 {
        background-color: beige;
      }
    </style>
  </head>
  <body>
    <nav class="top_menu">
      <a href="#sec1">1번영역</a>
      <a href="#sec2">2번영역</a>
      <a href="#sec3">3번영역</a>
      <a href="#sec4">4번영역</a>
      <a href="#sec5">5번영역</a>
    </nav>
    <a href="#sec1" class="goTop">상단으로</a>
    <main class="sec_con">
      <section id="sec1">1번 섹션</section>
      <section id="sec2">2번 섹션</section>
      <section id="sec3">3번 섹션</section>
      <section id="sec4">4번 섹션</section>
      <section id="sec5">5번 섹션</section>
    </main>
    <script>
      const $sections = [...document.querySelectorAll('.sec_con > section')];
      const $navLinks = document.querySelectorAll('nav > a');
      const navLinkOffset = 200; // navLink offset

      //wheel up/down 시 이전/다음 섹션
      window.addEventListener('wheel', function (e) {
        const currentSectionNumber = checkCurrentSectionNumber();
        e.deltaY > 0
          ? window.scrollTo(
              0,
              $sections[currentSectionNumber]?.offsetTop ||
                $sections[currentSectionNumber - 1].offsetTop
            ) // 휠 다운
          : window.scrollTo(0, $sections[currentSectionNumber - 2]?.offsetTop); // 휠 업
      });

      //scroll 시 top_menu 색 변경 로직
      window.addEventListener('scroll', function (e) {
        const $topMenu = document.querySelector('.top_menu');
        window.scrollY > 100
          ? $topMenu.classList.add('on')
          : $topMenu.classList.remove('on');
        // sectionsTopList[index] 넘어가면 해당 index section으로 이동 (index+1)
        for (var i = $sections.length - 1; i >= 0; i--) {
          if (window.scrollY > $sections[i].offsetTop - navLinkOffset) {
            $navLinks[i].classList.add('on');
            $navLinks[i + 1]?.classList.remove('on');
            $navLinks[i - 1]?.classList.remove('on');
            break;
          }
        }
      });
      // 개선 - 맨 위가 아닌 100px 아래에서 판단
      function checkCurrentSectionNumber() {
        let checkOffsetY = 100;
        for (var i = 0; i < $sections.length - 1; i++) {
          const $section = $sections[i];
          if (
            $section.offsetTop <= window.scrollY + checkOffsetY &&
            window.scrollY + checkOffsetY < $sections[i + 1].offsetTop
          )
            return i + 1;
        }
        return $sections.length; // 마지막 섹션
      }
    </script>
  </body>
</html>

참고 코드 - 스크립트만

이벤트 위임으로 처리

isScrolling - UX 개선

 <script>
      const $sections = [...document.querySelectorAll('.sec_con > section')];
      const $navLinks = document.querySelectorAll('nav > a')[0];
      let timerId = -1;
      let isScrolling = false;
      const scrollToSection = ($section, deltaY) => {
        if (isScrolling) return;
        isScrolling = true;
        const $prevSection = $section.previousElementSibling;
        const $nextSection = $section.nextElementSibling;
        deltaY > 0
          ? window.scrollTo(0, $nextSection?.offsetTop ?? $section.offsetTop)
          : window.scrollTo(0, $prevSection?.offsetTop ?? $section.offsetTop);
        timerId = setTimeout(() => {
          isScrolling = false;
          clearTimeout(timerId);
        }, 800);
      };
      const onWheel = function (e) {
        e.preventDefault();
        const { target, deltaY } = e;
        if (!target.closest('.sec_con')) return; //wheel 대상이 sec_con의 자식요소인지 확인합니다.
        const $section = target.closest('.sec_con > section'); // wheel 대상이 section의 자식 요소일 수도 있으므로 해당하는 section으로 $section을 할당
        //target 요소는 section임
        scrollToSection($section, deltaY);
      };

      document.addEventListener('wheel', onWheel, { passive: false });
    </script>
profile
프론트엔드 정리

0개의 댓글