문제해결

[문제해결] - Myblog[project] - JavaScript 스크롤기반 페이지

hyeeoooook 2026. 1. 18. 16:44

문제 해결 기간 : 2026.01.12 ~ 2026.01.18

 

원인 : html에서 h-screen은 각 기능마다 y값이 다르기 때문에, JS코드의 offsetTop값을 비교할때 편차가 존재한다.

 

 

영상처럼 스크롤중에 index 값 2가 두번 출력되면서 마지막 섹션까지 높이값을 이용한 조건에 편차가 존재한다는것을 알았다.

 

실제로 콘솔을 찍어본 결과

1번 인덱스 높이 : 1111

2번 인덱스 높이 : 2224

 

이런식으로 찍히는데 실제 사용자는 모니터에 따라 다른 높이값을 출력했다.

24인치 모니터 기준

1번 인덱스 높이 : 1111

2번 인덱스 높이 : 2223.33341

등등...

 

때문에 정확한 높이의 값을 지정하기엔 어려움이 있음을 깨달았다.

 


기존 JS 코드

navControler();

function navControler() {
  const mainContainer = document.querySelector(".maincontainer");
  const section = document.querySelectorAll(".maincontainer section");
  const asideBtn = document.querySelectorAll(".about_aside_btn");
  
  function getCurrentSectionIndex(scrollTop) {
    for (let i = 0; i < section.length; i++) {
      const currentScroll = section[i];
      const nextScroll = section[i + 1];
      
      if (!nextScroll && scrollTop >= currentScroll.offsetTop) {
        console.log("last section");
        return i;
      }
      else if(nextScroll && scrollTop >= currentScroll.offsetTop && scrollTop < nextScroll.offsetTop) {
        console.log("section index:", i);
        return i;
      }
    }
    return -1;
  }

  function actionAsideNav(index) {
    asideBtn.forEach((btn, i) => {
      if (i === index) {
        btn.classList.remove("w-[15px]");
        btn.classList.add("w-[30px]");
      } else {
        btn.classList.remove("w-[30px]");
        btn.classList.add("w-[15px]");
      }
    });
  }
  
  mainContainer.addEventListener("scroll", () => {
    const index = getCurrentSectionIndex(mainContainer.scrollTop);
    if (index !== -1) {
      actionAsideNav(index);
    }

  });
}

조건문에 scrollTop이 현재 인덱스의 높이값과 다음 인덱스의 높이값 사이에 있다면 로그를 출력하고 btn 네비게이션의 w값을 다시 재조정하는 방법을 채택했었으나, 위의 문제가 있음을 인지하였고 때문에 다음과 같은 해결책을 채택하였다.

 


1차 수정 JS 코드

navControler();

function navControler() {
  const mainContainer = document.querySelector(".maincontainer");
  const section = document.querySelectorAll(".maincontainer section");
  const asideBtn = document.querySelectorAll(".about_aside_btn");

  function getCurrentSectionIndex(currentScroll) {
    for (let i = 0; i < section.length; i++) {
      const currentSectionTop = section[i].offsetTop;
      const currentSectionHeight = section[i].offsetHeight;
      const threshold = currentSectionTop + currentSectionHeight / 2; // 섹션의 중간지점

      // 현재 스크롤이 현재 섹션과 다음 섹션의 중간지점 범위 내에 있으면
      if (currentScroll >= currentSectionTop && currentScroll < threshold + currentSectionHeight / 2) {
        console.log("section index:", i);
        console.log("Current Scroll Position:", currentScroll);
        console.log("Section OffsetTop:", currentSectionTop);
        return i;
      }
    }
    return -1;
  }

  function actionAsideNav(index) {
    asideBtn.forEach((btn, i) => {
      if (i === index) {
        btn.classList.remove("w-[15px]");
        btn.classList.add("w-[30px]");
      } else {
        btn.classList.remove("w-[30px]");
        btn.classList.add("w-[15px]");
      }
    });
  }

  mainContainer.addEventListener("scroll", () => {
    const currentScroll = mainContainer.scrollTop;

    const index = getCurrentSectionIndex(currentScroll);
    actionAsideNav(index);
  });
}

중간 범위를 지정해서 해결방안을 도출하고자 하였다.

 

하지만, 이 또한 사용자가 보는 모니터의 크기와 웹 페이지의 확대 상태에 따라 다른 좌표값을 출력해서 올바른 해결책이 아니었다.

 


최종 수정 코드

navControler();
function navControler(){
  const mainContainer = document.querySelector(".maincontainer");
  const section = document.querySelectorAll(".maincontainer section");
  const asideBtn = document.querySelectorAll(".about_aside_btn");

  const sections = document.querySelectorAll(".maincontainer section");

  let isFirstLoad = true;

  const observerOptions = {
    root: mainContainer,
    threshold: 0.5
  };

  const observer = new IntersectionObserver((entries) => {
    if (isFirstLoad) {
      actionAsideNav(1, asideBtn);
      isFirstLoad = false;
      return;
    }

    entries.forEach((entry) => {
      if(entry.isIntersecting){
        const sectionNum = Array.from(sections).indexOf(entry.target) + 1;
        actionAsideNav(sectionNum, asideBtn);
      }
    });
  }, observerOptions);

  section.forEach((sec) => {
    observer.observe(sec);
  });
}

  function actionAsideNav(sectionNum, asideBtn) {
    asideBtn.forEach((btn, i) => {
      if (i === sectionNum - 1) {
        btn.classList.remove("w-[15px]");
        btn.classList.add("w-[30px]");
      } else {
        btn.classList.remove("w-[30px]");
        btn.classList.add("w-[15px]");
      }
    });
  }

observer를 통해 계속 페이지를 감시하여 섹션 페이지를 확인하고 해당 페이지가 아니라면 인덱스 값 즉, sectionNum을 바꾸는 방법을 채택했다.

 

장점 

 - 높이값을 쓰지 않아서 모든 기기에 호환이 된다.

 - scoll 이벤트를 사용하는것 보다 스레드의 점유가 낮아 성능 저하 가능성이 낮아짐

단점

 - threshold 사용을 통해 만약 두 섹션이 동시에 보이는 상황이 발생하면 복수의 entry 값을 받을 수 있음.

 - indexOf 비용 발생 / 시간 복잡도 : O(n) / 섹션의 갯수가 적어 거의 무시 가능한 수준임. 만약 섹션이 많다면 바꿔야하는 요소중 하나