react 스크롤시 네비게이션 변환

이경준·2021년 5월 17일
3

빵덕

목록 보기
1/7


진행하고 있는 빵덕 프로젝트의 테마상세 페이지에서 스크롤시 네비게이션바의 색이 변환하는 디자인이 나왔다. 스크롤 위치를 어떻게확인할까 고민을 하다가 IntersectionObserver를 사용하기로 하였다.

1. 태그위치 확인하기

const option = {};
const isRef = useRef<HTMLElement | null>(null);

useEffect(() => {
    const observer = new IntersectionObserver((entry) => {
      if (entry[0].isIntersecting) {
        console.log("여깁니다")
      }
    }, option);

    if (isRef.current) {
      observer.observe(isRef.current);
    }

    return () => observer.disconnect();
  }, []);

return (
    <section>
      <ThemeHeader />
      <ThemeNav />
      <ThemeExplain ref={isRef}/>
      <ThemeAnalysis />
      <ThemeEvent />
      <ThemeRevie />
      <ReviewBottom />
    </section>
  );

1.eEffect안에 IntersectionObserver로 ref를 감지하도록 하고 태그가 화면에 나옴과 동시에 isIntersecting의 true라면 console을 찍을수 있게하여 확인하였다.
2.섹션별로 IntersectionObserver가 필요해보여서 여러개를 만드려니 코드가 지저분해질것같아 customHook으로 만들어야곘다는 생각을 하였다.

2. 커스텀훅으로 빼기

// useThemeObserver.ts
import { useEffect, useRef } from 'react';

export const useThemeObserver = (
  setState: React.Dispatch<React.SetStateAction<number>>,
  stateNumber: number,
): React.MutableRefObject<HTMLElement | null>[] => {
  const isRef = useRef<HTMLElement | null>(null);
  const option = {};

  useEffect(() => {
    const observer = new IntersectionObserver((entry) => {
      if (entry[0].isIntersecting) {
        setState(stateNumber);
      }
    }, option);

    if (isRef.current) {
      observer.observe(isRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return [isRef];
};

커스텀훅안에 state번호를 지정하여 번호의 값으로 네비게이션바를 구분하기로 하였다.

3. 태그별로 useRef 지정하기

const ThemeDetailTemplate = (): ReactElement => {
  const [navNumber, setNavNumber] = useState(1);
  
  const [explainRef] = useThemeObserver(setNavNumber, 1);
  const [analysisRef] = useThemeObserver(setNavNumber, 2);
  const [eventRef] = useThemeObserver(setNavNumber, 3);
  const [reviewRef] = useThemeObserver(setNavNumber, 4);

  return (
    <section>
      <ThemeHeader />
      <ThemeNav position={navNumber} />
      <ThemeExplain isRef={explainRef} />
      <ThemeAnalysis isRef={analysisRef} />
      <ThemeEventisRef={eventRef}/>
      <ThemeReview isRef={reviewRef} />
      <ReviewBtn/>
    </section>
  );
};

export default ThemeDetailTemplate;
  1. 화면에 원하는 섹션이 올라올때마다 setState로 값을 바꿔서 섹션별로 번호를 알수있다.
  2. 그러나 섹션의 높이가 좁아서인지 스크롤을 올렸다가 내릴떄 1번에서 3번으로 바로 넘어가는 버그가 있었다.

4. rootMargin으로 섹션 위치 좁히기

//usethemeObserver.ts
const option = { threshold: 0, rootMargin: `-${document.body.scrollHeight / 2 - 1}px 0px` };

IntersectionObserver의 option에서 rootMargin값으로 화면높이의 반보다 약간작게하여 중앙을 지나갈때 state번호가 바뀔수 있게하여 1에서 3으로 넘어가는 불상사를 막을수있게 하였다.

5. 네비게이션바 css 적용하기

//ThemeNav.tsx
interface Props {
  position: number;
}
const ThemeNav = ({ position }: Props): ReactElement => {
  const arr = [
    { id: 1, content: '테마' },
    { id: 2, content: '분석' },
    { id: 3, content: '이벤트' },
    { id: 4, content: '리뷰' },
  ];

  return (
    <S.Nav>
      {arr.map((ar) => (
        <S.Box key={ar.id} border={position === ar.id}>
          <S.PTag border={position === ar.id}>{ar.content}</S.PTag>
        </S.Box>
      ))}
    </S.Nav>
  );
};

네비게이션바와 props로 받아온 state값이 같을때의 boolean값을 styled-component로 넘겨 state값이 변경될때마다 css가 변환되게 구현해보았다.

profile
내가 기억하기위한 블로그

2개의 댓글

comment-user-thumbnail
2022년 11월 23일

훅이 정말 깔끔하네요!
덕분에 많은 도움 됐습니다 :)

1개의 답글