📌 작성자 개발 환경
OS: Mac OS
Tool: Visual Studio Code

3 분 소요

til

메인 페이지 구성

웹페이지의 메인 페이지가 되는 페이지를 구현했다.

구성

  • 5개의 섹션으로 나눠진 페이지
    • 스크롤을 통한 섹션 이동
    • 사이드바 섹션 네비게이션을 통한 이동
  • 각각 웹페이지를 소개해주는 페이지로 구성

섹션 1: 캐러셀을 통한 페이지 소개
섹션 2: 애니매이션을 통한 제품 소개
섹션 3: 애니매이션을 통한 제품 소개
섹션 4: 애니매이션을 통한 주문 방법 소개
섹션 5: 커스텀 제품 시안 리뷰들

메인 페이지 (Main.tsx)


전체 코드

//Main.tsx
import { useState, useEffect } from "react";
import styled from "styled-components";
import MainSection1 from "../../components/Main/MainSection1";
import MainSection2 from "../../components/Main/MainSection2";
import MainSection3 from "../../components/Main/MainSection3";
import MainSection4 from "../../components/Main/MainSection4";
import MainSection5 from "../../components/Main/MainSection5";
import MainSidebar from "../../components/Main/MainSidebar";
import ModalPortal from "../../share/ModalPortal";
import { throttle } from "../../utils/Throttle";
const MainContainer = styled.div`
  height: 100vh;
  overflow: hidden;
`;

const AnimatedSection = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 500ms ease-in-out;
  pointer-events: none;
  &.active {
    opacity: 1;
    pointer-events: auto;
  }
`;

function Main() {
  const [activeSection, setActiveSection] = useState(1);

  const handleScrollToSection = (section: number) => {
    setActiveSection(section);
  };

  useEffect(() => {
    const handleWheel = throttle((event: WheelEvent) => {
      event.preventDefault();

      const scrollPosition = window.scrollY;

      if (event.deltaY < 0 && activeSection > 1) {
        setActiveSection(activeSection - 1);
      } else if (event.deltaY > 0 && activeSection < 5) {
        setActiveSection(activeSection + 1);
      }

      setTimeout(() => {
        window.scrollTo({ top: scrollPosition });
      }, 0);
    }, 1000);

    window.addEventListener("wheel", handleWheel, { passive: false });

    return () => {
      window.removeEventListener("wheel", handleWheel);
    };
  }, [activeSection]);

  useEffect(() => {
    const handleScroll = () => {
      const sections = Array.from(document.querySelectorAll(".section"));
      const sectionOffsets = sections.map(
        (section) => section.getBoundingClientRect().top + window.pageYOffset
      );

      const scrollPosition = window.scrollY + window.innerHeight / 2;

      const activeIndex = sectionOffsets.findIndex(
        (offset) => offset > scrollPosition
      );

      if (activeIndex !== -1) {
        setActiveSection(activeIndex + 1);
      }
    };

    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return (
    <MainContainer>
      <ModalPortal>
        <MainSidebar
          activeSection={activeSection}
          handleScrollToSection={handleScrollToSection}
        />
      </ModalPortal>
      <AnimatedSection className={activeSection === 1 ? "active" : ""}>
        <MainSection1 id={"section1"} />
      </AnimatedSection>
      <AnimatedSection className={activeSection === 2 ? "active" : ""}>
        <MainSection2 id={"section2"} isActive={activeSection === 2} />
      </AnimatedSection>
      <AnimatedSection className={activeSection === 3 ? "active" : ""}>
        <MainSection3 id={"section3"} isActive={activeSection === 3} />
      </AnimatedSection>
      <AnimatedSection className={activeSection === 4 ? "active" : ""}>
        <MainSection4 id={"section4"} isActive={activeSection === 4} />
      </AnimatedSection>
      <AnimatedSection className={activeSection === 5 ? "active" : ""}>
        <MainSection5 id={"section5"} />
      </AnimatedSection>
    </MainContainer>
  );
}

export default Main;

Main 컴포넌트 정의와 상태 설정


function Main() {
  const [activeSection, setActiveSection] = useState(1);

activeSection이라는 상태 변수를 사용하여 현재 활성화된 섹션을 추적

handleScrollToSection 함수


const handleScrollToSection = (section: number) => {
  setActiveSection(section);
};

handleScrollToSection 함수는 MainSidebar 컴포넌트에서 사용되며, 클릭한 섹션으로 스크롤을 이동시키는 역할

스크롤 이벤트 핸들링을 통한 섹션 변경


useEffect(() => {
  const handleWheel = throttle((event: WheelEvent) => {
    event.preventDefault();

    const scrollPosition = window.scrollY;

    if (event.deltaY < 0 && activeSection > 1) {
      setActiveSection(activeSection - 1);
    } else if (event.deltaY > 0 && activeSection < 5) {
      setActiveSection(activeSection + 1);
    }

    setTimeout(() => {
      window.scrollTo({ top: scrollPosition });
    }, 0);
  }, 1000);

  window.addEventListener("wheel", handleWheel, { passive: false });

  return () => {
    window.removeEventListener("wheel", handleWheel);
  };
}, [activeSection]);

스크롤 이벤트를 감지하고, 스크롤 방향에 따라 활성 섹션을 변경하는 역할 throttle 함수는 스크롤 이벤트를 일정 간격으로 제어하여 부하를 줄이는 역할

스크롤 이벤트 핸들링을 통한 현재 활성 섹션 변경


useEffect(() => {
  const handleScroll = () => {
    const sections = Array.from(document.querySelectorAll(".section"));
    const sectionOffsets = sections.map(
      (section) => section.getBoundingClientRect().top + window.pageYOffset
    );

    const scrollPosition = window.scrollY + window.innerHeight / 2;

    const activeIndex = sectionOffsets.findIndex(
      (offset) => offset > scrollPosition
    );

    if (activeIndex !== -1) {
      setActiveSection(activeIndex + 1);
    }
  };

  window.addEventListener("scroll", handleScroll);
  return () => {
    window.removeEventListener("scroll", handleScroll);
  };
}, []);

스크롤 이벤트를 감지하여 현재 화면에 표시되는 섹션의 인덱스를 추적하고, 해당 섹션을 활성화시키는 역할

사이드바

MainSidebar 컴포넌트 정의 및 상태 설정


interface MainSidebarProps {
  activeSection: number;
  handleScrollToSection: (section: number) => void;
}

const MainSidebar: React.FC<MainSidebarProps> = ({ activeSection, handleScrollToSection }) => {
  const [clickedSection, setClickedSection] = useState(0);

  const handleClick = (section: number) => {
    setClickedSection(section);
    handleScrollToSection(section);
    setTimeout(() => setClickedSection(0), 300);
  };

handleClick 함수는 섹션 아이템 클릭 시 호출되는 함수

  • 해당 섹션을 활성화하고 스크롤을 해당 섹션으로 이동한다.
  • 클릭된 섹션은 스크롤 후 일정 시간 후에 원래 상태로 돌아오도록 설정한다.

사이드바 아이템 렌더링


return (
  <SidebarContainer>
    <SidebarItem
      active={activeSection === 1}
      clicked={clickedSection === 1}
      onClick={() => handleClick(1)}
      data-label="BUYTE"
    />
    <SidebarItem
      active={activeSection === 2}
      clicked={clickedSection === 2}
      onClick={() => handleClick(2)}
      data-label="BUYTE의 제품"
    />
    {/* 나머지 섹션 아이템들 */}
  </SidebarContainer>
);
  • 사이드바 아이템은 섹션 개수에 따라 반복하여 렌더링된다.
  • 각 아이템에는 해당 섹션의 활성 여부와 클릭 여부를 전달하여 스타일을 조정하고, 클릭 이벤트를 설정한다.
  • 마우스 오버 시 섹션 이름이 나타나고, 클릭 시 클릭 효과와 함께 활성 섹션이 변경된다.

결과

메인 페이지
til

🥲 그림판을 통해 직접 그린 그림들

til til til til til til

댓글남기기