GitHub Pages 배포 이후 첫 작업은 페이지를 섹션으로 나눠서
한 화면에서 스크롤링을 이용하여 섹션 별로 이동하게 하는 기능이다.
이를 풀페이지 스크롤링이라고 한다.
라이브러리를 사용하는 것과 직접 CSS와 JavaScript를 사용하여 구현하는 방법이 있는데,
나의 성장을 위해 진행하는 프로젝트이기 때문에 후자를 선택하였다.
✨ Styled-components
CSS는 styled-components를 사용하였다.
import styled from "styled-components";
const Container = styled.div`
height: 100vh; //페이지 높이를 전체 화면으로 설정
overflow: hidden; //스크롤 숨김
`;
const Section = styled.section`
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
scroll-snap-align: start; //스크롤 스냅 기능
`;
🖱️ 스크롤 스냅 기능
- 스크롤링해서 화면을 위, 아래로 이동할 때, 현재 화면과 다음 화면 사이의 절대 비율이 50%가 넘어갈 경우, 비율이 높은 쪽으로 자석처럼 달라붙는 효과를 구현해주는 기능
//섹션 나누기
function App() {
return (
<Container ref={containerRef}>
<Section>
<Main />
</Section>
<Section>
<Introduce />
</Section>
<Section>
<Career />
</Section>
<Section>
<Project />
</Section>
<Section>
<Footer />
</Section>
</Container>
);
}
}
먼저 각 페이지 별로 섹션을 나눠주었다.
여기서 Container에 있는 ref={containerRef} 를 볼 수 있다.
이것에 대해 알기 위해서 먼저 useRef에 대해 알아야한다.
코드를 보기 전에 먼저 사용할 React Hook들에 대해서 알아보았다.
📖 useState
state를 사용하기 위한 Hook
- 사용
const [변수명, set함수명] = useState(초기값);
📖 useRef
React Hook의 한 종류로 특정한 DOM요소에 접근이 가능하여 불필요한 재렌더링 X
형식
- 생성
const containerRef = useRef(null);
- 결과 값으로 {current : 초기값} 을 지닌 객체 반환 (current라는 키값을 지닌 프로퍼티가 생성, 값에 변경을 줄 때 current 이용)
- 반환 요소에 접근
<Container ref={containerRef}>
- 특징
- 반환된 useRef 객체는 컴포넌트의 전생애주기를 통해 유지
- 컴포넌트가 계속해서 렌더링 되어도 컴포넌트가 언마운트되기 전까지 값 유지
- current 속성은 값을 변경해도 상태 변경처럼 컴포넌트 재렌더링 X (렌더링과 상과없이, 마운트된 시점 → 언마운트된 시점까지 값 유지)
- 렌더링 이후에도 값은 유지, 변수는 초기화
- 사용
- 값을 저장하는 저장공간
- 변경 시 렌더링을 발생시키지 않아야 할 값을 다룰 때 사용(변화만 감지)
- DOM요소에 접근
- 예시 : input 요소를 클릭하지 않아도 focus를 줄 때
- 값을 저장하는 저장공간
- 장점
- state에 값을 담으면, 변경될 때마다 재렌더링 → 성능 저하
- useRef 사용 시 값이 변경되도 렌더링 X → .성능 향상
📖 useEffect
React의 함수 컴포넌트에서 Side effect(부작용)를 실행할 수 있게 하기 위한 Hook
다른 커모넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될수 없기 때문
- 사용
useEffect(() => {
// 매 렌더링마다 실행
});
useEffect(() => {
// 컴포넌트가 처음 렌더링된 실행
}, []);
useEffect(() => {
// 컴포넌트가 처음 렌더링된 이후 실행
// a나 b가 변경되어 컴포넌트가 재렌더링된 이후 실행
}, [a, b]);
📖 useCallback
값이 아닌 함수를 반환
- 사용
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
이제 구현한 코드를 살펴보도록 하자
handleScroll 함수
사용자가 마우슬 휠을 움직일 때마다 호출되는 이벤트 헨들러
useCallback 훅을 사용해 메모이제이션 되어있음
이 함수가 종속된 값들이 변경되지 않는 한, 컴포넌트가 다시 렌더링될 때마다 새로운 함수로 재생성되지 않음
- 함수 정의
// useCallback으로 (isThrottled, sectionCount)이 변경되지 않는 한 동일한 함수 인스턴스 유지
const handleScroll = useCallback(
(event) => {
if (isThrottled) return; // 스크롤 이벤트가 너무 자주 발생하지않게 방지
// 스크롤 이벤트를 처리하는 동안 isThrottled 상태를 true로 설정, 다음 스크롤 이벤트 처리 방지
setIsThrottled(true);
// 스크롤 이벤트가 너무 자주 발생하지 않도록 스로틀링(throttling) 기법을 사용합니다.
- 스크롤 방향에 따른 섹션 변경
const deltaY = event.deltaY;
// event.deltaY: 마우스 휠 스크롤의 수직 이동량을 나타냅니다.
if (deltaY > 0) {
//스크롤이 아래로 이동한 경우
setCurrentSection((prev) => Math.min(prev + 1, sectionCount - 1));
//이전 섹션 인덱스에서 +1, 최대 섹션 수를 초과하지 않도록 제한
} else {
//스크롤이 위로 이동한 경우
setCurrentSection((prev) => Math.max(prev - 1, 0));
//이전 섹션 인덱스에서 -1, 최솟값인 0을 벗어나지 않도록 제한
}
// 스크롤 방향에 따라 현재 섹션을 업데이트합니다.
- 스크롤링 설정
//800ms 후에 isThrottled 상태를 다시 false로 설정하여 다음 스크롤 이벤트 처리할 수 있게 함
setTimeout(() => setIsThrottled(false), 800);
},
[isThrottled, sectionCount] // 의존성 배열 설정으로 값이 변경될 때마다 함수 다시 생성
);
useEffect 훅을 활용한 이벤트 리스너 관리 및 스크롤 제어
React에서 useEffect를 사용하여 컴포넌트가 마운트될 때 특정 이번트 리스터를 추가하고,
언마운트될 때 이를 제거하는 방법으로, 상태 변화에 따라 동적으로 스크롤 위치를 변경하는 기능 구현
첫 번째 useEffect
- 이벤트 리스너 관리
- 컴포넌트가 마운트될 때 wheel 이벤트 리스너를 추가하고, 언마운트될 때 제거하는 코드
const containerRef = useRef(null);
// useRef 훅을 사용해 참조 객체 생성, 이 객체는 DOM 요소 참조
useEffect(() => {
const container = containerRef.current;
// containerRef의 현재 값(즉, DOM 요소)을 가져옴
container.addEventListener("wheel", handleScroll);
// wheel 이벤트가 발생할 때 호출할 이벤트 핸들러(handleScroll) 추가
return () => {
container.removeEventListener("wheel", handleScroll);
// 컴포넌트가 언마운트되거나 의존성(handleScroll)이 변경될 때 이벤트 리스너 제거
};
}, [handleScroll]);
// 의존성 배열에 handleScroll을 추가하여 handleScroll이 변경될 때마다 이펙트가 다시 실행
두 번째 useEffect
- 상태 변화에 따른 스크롤 제어
- currentSection 상태가 변경될 때마다 컨테이너를 해당 섹션 위치로 부드럽게 스크롤
useEffect(() => {
const container = containerRef.current;
// containerRef의 현재 값(DOM 요소)을 가져옴
container.scrollTo({
top: currentSection * window.innerHeight,
// currentSection 값에 따라 스크롤할 Y 좌표 계산
behavior: "smooth",
// 스크롤이 부드럽게 이동하도록 애니메이션 설정
});
}, [currentSection]);
// currentSection이 변경될 때마다 이펙트 다시 실행
✨ 느낀 점
라이브러리 없이 직접 구현하는 것에 대한 어려움에 대해서 깨달았다. 해보지 않은 것을 맨땅에서 시작하는 것이 좀 쉽지않았다. 하지만 검색해보고 몰랐던 것에 대해 알게 되고 코드를 따라쳐보고 내가 원하는대로 수정하며 구현하다 보니 코드에 대해 이해하게 되었다.
처음에는 받아쓰기를 하는 느낌이었는데 그 받아쓴 코드를 이해하고 내가 원하는 대로 수정하면서 나의 것이 되었다.
어떠한 기능을 구현하고자 하면 어떠한 React Hook이 사용되어야할지 잘 생각하고 코드를 짜야할 것 같다.
'STUDY > React' 카테고리의 다른 글
[React 포트폴리오 페이지 제작기] 1. github.io로 배포하기 (0) | 2024.09.05 |
---|