[포트폴리오] full-page

cheolmin·2024년 1월 20일

커스텀 한 이유

포트폴리오를 만들기 위해 레퍼런스를 둘러보고 있는데, 하나같이 스크롤을 내리면 섹션이 바뀌는 구조였다. 그래서 구글에 쳐보니 fullpage라고 한다고 한다.

좀 더 찾아보니 fullpage.js라는 라이브러리가 있긴 한데 제이쿼리라서 일단 패쓰..
react-full-page라는 라이브러리가 있어서 적용을 해 봤는데 로컬에서는 잘 나오는데 vercel로 배포를 하면 아무것도 안 뜨는 이슈가 발생했다.
게다가 레퍼런스 자료가 너어어무 없었다..
오히려 커스텀 한 레퍼런스 자료가 많을 정도로 !

그래서 그냥 수작업으로 만들기로 결정 !!


구조 짜기

1단계 : 기본코드와 CSS 구성
2단계 : 마우스 휠 이벤트 함수 구성

Step 1

body {
  margin: 0;
  overflow-y: hidden;
}

.outer {
  height: 100vh;
  overflow-y: auto;
}

/* 화면에서 스크롤바 안보이게 */
.outer::-webkit-scrollbar {
  display: none;
}

.inner {
}

.bg-yellow {
  background-color: #f7f6cf;
}

.bg-blue {
  background-color: #b6d8f2;
}

.bg-pink {
  background-color: #f4cfdf;
}

가상 클래스를 설정해 display 속성을 none으로 설정해 스크롤이 동작은 하지만 눈에는 안 보이게 구현

import { useRef } from "react";

import "./App.css";

function App() {
  const outerDivRef = useRef();
  return (
    <div ref={outerDivRef} className="outer">
      <div className="inner bg-yellow">1</div>
      <div className="inner bg-blue">2</div>
      <div className="inner bg-pink">3</div>
    </div>
  );
}

export default App;

Step 2

import { useEffect, useRef } from "react";

import "./App.css";

function App() {
  const outerDivRef = useRef();
  useEffect(() => {
    const wheelHandler = (e) => {
      e.preventDefault();
      // 스크롤 행동 함수
    };
    const outerDivRefCurrent = outerDivRef.current;
    outerDivRefCurrent.addEventListener("wheel", wheelHandler);
    return () => {
      outerDivRefCurrent.removeEventListener("wheel", wheelHandler);
    };
  }, []);
  return (
    <div ref={outerDivRef} className="outer">
      <div className="inner bg-yellow">1</div>
      <div className="inner bg-blue">2</div>
      <div className="inner bg-pink">3</div>
    </div>
  );
}

export default App;

outer에 스크롤이 걸려있고, 그 outer (classNmae =outer)에 wheel 이벤트 핸들러를 추가

휠 이벤트는 마우스의 휠을 움직일 때마다 발생.

그니까 outer의 outerDivRef가 outerDivRefCurrent와 연결돼 있어서, outerDivRefCurrent가 실행된다.


Step 3

wheelHandler 함수

event 객체를 파라미터로 받고, 필요한 로직은 두 개다.

  1. 마우스 휠이 움직인 방향 (위로 가는 지, 아래로 가는 지)
  2. 현재 페이지

마우스 휠이 움직인 방향 같은 경우 event 객체 내부에 있는 deltaY라는 값으로 구할 수 있다.
delta Y가 양수면 마우스 휠을 아래로 굴린거고, 다음페이지로 넘어가는 것.
delta Y가 음수면 마우스 휠을 위로 굴린거고, 이전페이지로 돌아가는 것.

현재 페이지는 outerDiv의 scrollTOp과 window 객체의 innerHeight 값을 활용해 wheel 이벤트 발생 시점의 현재페이지를 확인할 수 있다.

scrollTop은 스크롤바 막대 위쪽의 위치이고 innerHeight은 브라우저 화면의 세로길이

wheel 함수의 로직은 다음과 같다

const wheelHandler = (e) => {
  e.preventDefault();
  const { deltaY } = e;
  const { scrollTop } = outerDivRef.current; // 스크롤 위쪽 끝부분 위치
  const pageHeight = window.innerHeight; // 화면 세로길이, 100vh와 같습니다.
  
  if (deltaY > 0) {
    // 스크롤 내릴 때
    if (현재 1페이지면) {
      // 2페이지로 이동
    } else if (현재 2페이지면) {
      // 3페이지로 이동
    } else (현재 3페이지면) {
      // 3페이지로 이동
    }
  } else {
    // 스크롤 올릴 때
    if (현재 1페이지면) {
      // 1페이지로 이동
    } else if (현재 2페이지면) {
      // 1페이지로 이동
    } else (현재 3페이지면) {
      // 2페이지로 이동
    }
  }
};

정리를 해보자

const wheelHandler = (e) => {
  e.preventDefault();
  const { deltaY } = e;
  const { scrollTop } = outerDivRef.current; // 스크롤 위쪽 끝부분 위치
  const pageHeight = window.innerHeight; // 화면 세로길이, 100vh와 같습니다.

  if (deltaY > 0) {
    // 스크롤 내릴 때
    if (scrollTop >= 0 && scrollTop < pageHeight) {
      //현재 1페이지
      console.log("현재 1페이지, down");
      outerDivRef.current.scrollTo({
        top: pageHeight,
        left: 0,
        behavior: "smooth",
      });
    } else if (scrollTop >= pageHeight && scrollTop < pageHeight * 2) {
      //현재 2페이지
      console.log("현재 2페이지, down");
      outerDivRef.current.scrollTo({
        top: pageHeight * 2,
        left: 0,
        behavior: "smooth",
      });
    } else {
      // 현재 3페이지
      console.log("현재 3페이지, down");
      outerDivRef.current.scrollTo({
        top: pageHeight * 2,
        left: 0,
        behavior: "smooth",
      });
    }
  } else {
    // 스크롤 올릴 때
    if (scrollTop >= 0 && scrollTop < pageHeight) {
      //현재 1페이지
      console.log("현재 1페이지, up");
      outerDivRef.current.scrollTo({
        top: 0,
        left: 0,
        behavior: "smooth",
      });
    } else if (scrollTop >= pageHeight && scrollTop < pageHeight * 2) {
      //현재 2페이지
      console.log("현재 2페이지, up");
      outerDivRef.current.scrollTo({
        top: 0,
        left: 0,
        behavior: "smooth",
      });
    } else {
      // 현재 3페이지
      console.log("현재 3페이지, up");
      outerDivRef.current.scrollTo({
        top: pageHeight,
        left: 0,
        behavior: "smooth",
      });
    }
  }
};

Step 4 Dots 만들기

우측에 점을 3개 만들어 현재 페이지 순서의 점만 칠하는 로직 추가하기

//Dots.js

const Dot = ({ num, currentPage }) => {
  return (
    <div
      style={{
        width: 10,
        height: 10,
        border: "1px solid black",
        borderRadius: 999,
        backgroundColor: currentPage === num ? "black" : "transparent",
        transitionDuration: 1000,
        transition: "background-color 0.5s",
      }}
    ></div>
  );
};

const Dots = ({ currentPage }) => {
  return (
    <div style={{ position: "fixed", top: "50%", right: 100 }}>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          justifyContent: "space-between",
          alignItems: "center",
          width: 20,
          height: 100,
        }}
      >
        <Dot num={1} currentPage={currentPage}></Dot>
        <Dot num={2} currentPage={currentPage}></Dot>
        <Dot num={3} currentPage={currentPage}></Dot>
      </div>
    </div>
  );
};

export default Dots;

currentPage를 인자로 받고 dot에 num과 currentPage를 넘겨 현재 페이지와 숫자가 일치한다면 색깔을 다르게 삼항연산자로 스타일을 적용

//App.tsx

const [currentPage, setCurrentPage] = useState(1);

  return (
    <div ref={outerDivRef} className="outer">
      <Dots currentPage={currentPage} />
      <div className="inner bg-yellow">1</div>
      <div className="divider"></div>
      <div className="inner bg-blue">2</div>
      <div className="divider"></div>
      <div className="inner bg-pink">3</div>
    </div>
  );
}
profile
부딪히고 생각하자

0개의 댓글