[Book Buddy / Function] 클릭한 메뉴로 스크롤 이동하기

클릭한 메뉴의 위치로 스크롤이 이동하는 로직을 구현했다.

간단하지만 타입스크립트 사용, 사이드바와 별개의 컴포넌트인 바디, 헤더 높이 등 고려해야 할 여러 조건때문에 은근히 고민하게 만든 친구다. 

 

 

1. useRef에 이동할 태그 저장하기

// mypage component

  const navScrollListRef = useRef<any>([]);
  
  return(
  
  	// ...중략
    
     <div ref={el => { navScrollListRef.current[0] = el; }}>
      <MypageTable title="마이페이지" />
    </div>
    
      <div ref={el => { navScrollListRef.current[1] = el; }}>
      <MypageTable title="전체 주문 내역" />
    </div>
    
      <div ref={el => { navScrollListRef.current[2] = el; }}>
      <AskTable title="1:1 문의 내역" />
    </div>

	// ...중략
    
  )

 

 

일단 useRef를 사용하여 스크롤을 이동시킬 div의 위치를 저장한다. 사이드바의 메뉴 개수만큼 있어야 하므로 여러개를 하나의 useRef에 저장하기 위해 초기값을 배열로 준다. 

return문 안에서 컴포넌트를 div로 감싸고 el(element)를 배열에 저장한다.

 

발생한 오류 1  :

더보기

 

 const navScrollListRef = useRef<htmldivelement[]>([]);

 

여기서 처음에 타입을 <htmldivelement[]>로 주었는데 div element를 저장하는 곳에서 이런 에러가 났다. 

tsconfig.json에 "strictNullChecks": true 설정이 되어있기 때문인데 설정을 꺼주거나(false) 타입을 강제로 할당하는 방법으로 해결할 수 있다 (https://stackoverflow.com/questions/48488701/type-null-is-not-assignable-to-type-htmlinputelement-reactjs). 나는 크게 타입 문제가 생길 부분이 아니라는 판단 하에 두 방법 모두 사용하지 않고 타입을 any로 주어 해결했다.

 

발생한 오류 2 :

더보기

 

<div ref={el => { navScrollListRef[0] = el; }}>
  <MypageTable title="마이페이지" />
</div>

 

처음에 el을 navScrollListRef[0]에 바로 저장하려고 했더니 만난 에러다.

배열은 .current 안에 존재한다. 

 

 

2.  사이드바 메뉴 클릭시 해당하는 인덱스 반환

// sidebar component 

  const handleScrollView = (e: any) => {
    const menu = e.target.innerText;
    const category: { [key: string]: number } = {
      마이페이지: 0,
      '전체 주문 내역': 1,
      '1:1 문의 내역': 2,
      '북마크 리스트': 3,
    };

    setNavScroll(category[menu]); //변수에 담기 (여기선 recoil 사용)
  };

 

1. 사이드바 컴포넌트에서 클릭한 메뉴를 e.target.innerText로 가져와

2. category의 key와 일치한 value값을 찾아

3. 변수에 저장하는

함수를 만든다.

 

  <div>
    <div>
      <span onClick={handleScrollView}>마이페이지</span>
    </div>
    <div>
      <span onClick={handleScrollView}>전체 주문 내역</span>
      <span onClick={handleScrollView}>1:1 문의 내역</span>
      <span onClick={handleScrollView}>북마크 리스트</span>
    </div>
  </div>

 

함수는 각각의 메뉴에 적용한다.

 

발생한 오류 :

더보기
  <div onClick={handleScrollView}>
    <div>
      <span>마이페이지</span>
    </div>
    <div>
      <span>전체 주문 내역</span>
      <span>1:1 문의 내역</span>
      <span>북마크 리스트</span>
    </div>
  </div>

 

좀 바보같은 실순데 처음에 onClick을 전체를 감싸는 div에 줘서 에러가 났다.

이렇게 하면 스크롤 작동은 잘 하지만 사이드바의 빈 공간을 눌렀을 때 not found 페이지로 이동한다.

 

3. useEffect에 스크롤 이동 코드 작성

 // mypage component
 
 const navScrollIndex = useRecoilValue(NavScrollAtom);
 
 useEffect(() => {
    // sidebar scroll
    if (navScrollListRef.current) {
      navScrollListRef.current[navScrollIndex].scrollIntoView({
        behavior: 'smooth',
      });
    }
  })

난 상태관리 라이브러리로 리코일을 사용했기 때문에 2번에서 recoil atom에 저장한 값을 가져와 navScrollListRef.current의 인덱스 값으로 넣어준 후 .scrollIntoView를 이용하여 스크롤을 이동시켰다. 

behavior을 smooth로 주면 스크롤이 부드럽게 이동한다.

 

 

4.  +스크롤 위치 세부조정

'전체 주문 내역'을 눌렀으나 스크롤이 애매한 위치로 이동된 모습

 

 

이번엔 이동은 되는 것 같지만 스크롤이 애매한 위치에 가 있다.

왜 이러는건지 고민을 한참 하다가 window 전체 화면을 기준으로 스크롤이 이동한 것이 아닌가 하는 생각이 들었다. 

즉 스크롤은 원하는 element로 잘 이동했지만 헤더를 침범한 것이다.

스크롤 위치 조정을 위해 검색을 열심히 하다가 스텍오버플로우에서 적절한 답변을 찾았다. 

 

.example {
  scroll-margin-top: 10px;
}

 

이렇게 스크롤에 margin을 넣을 수 있다고 한다 (https://stackoverflow.com/questions/24665602/scrollintoview-scrolls-just-too-far). 

 

 <div
  style={{ scrollMarginTop: '240px' }}
  ref={el => {
    navScrollListRef.current[1] = el;
  }}
>
  <MypageTable title="전체 주문 내역" />
</div>

 

이런 식으로 적당한 margin 값을 찾아 적용했고 아래와 같이 잘 작동하는 모습을 볼 수 있었다.

 

'전체 주문 내역' 타이틀이 보이도록 스크롤이 이동된 모습

 

 

 

 


참고자료

 

https://inthedev.tistory.com/27

https://stackoverflow.com/questions/24665602/scrollintoview-scrolls-just-too-far

https://stackoverflow.com/questions/48488701/type-null-is-not-assignable-to-type-htmlinputelement-reactjs