본문 바로가기
React

React(91) 탭 클릭시 지정 영역으로 스크롤 이동 / 스크롤시 해당 탭 자동선택

by 새발개발JA 2023. 1. 25.
반응형

 

이 녀석은 반응형 탭에 대한 고민의 흔적이 담긴 꽤 오래 묵혀놓은 포스팅이다.

재작년에 짰던 코드라 부족할 수 있지만 최대한 고쳐 올려보겠다.

 

상품 판매 페이지의 중간에 메뉴 탭이 붙게 되는데,

이 메뉴를 클릭시 해당 내용이 있는 곳으로 스크롤이 이동한다.

또한 그냥 스크롤 다운 하다가 해당 영역이 있는 지점에 들어서면 이번엔 반대로 탭이 해당 메뉴로 이동한다.

그럼 구현하러 가보자 !

 

 


 

React(91) 탭 클릭시 지정영역으로 스크롤 이동 / 스크롤시 해당 탭 자동선택

 

 

아래 상품 상세 페이지에는 포트폴리오라는 탭이 보인다. 이 탭을 클릭해보자

 

요렇게 스크롤이 딱 포트폴리오를 설명하는 부분으로 이동한다. 

반대의 경우도 마찬가지이다.

포트폴리오를 설명하는 부분으로 스크롤을 내리다보면 해당 영역으로 진입하면  탭이 눌리게 된다.

 

 

 

import { ReactElement, useEffect, useState, useRef } from 'react';
import Tabs from 'components/Tabs';
import Portfolio from 'components/Portfolio';
import DetailInfo from 'components/DetailInfo';

const menu = [
  { id: 0, name: '포트폴리오' },
  { id: 1, name: '상세정보' }
];
    
const ProductDetail = (): ReactElement => {
  const [menuID, setMenuID] = useState(0);
  const portfolioRef = useRef<HTMLDivElement>(null);
  const detailInfoRef = useRef<HTMLDivElement>(null);
  const scrollTarget = useRef<number | null>(null);
  const scrolling = useRef(false);
  const offset = 72; // 탭의 높이
 
  // 스크롤 이벤트 감지
  useEffect(() => {
    window.addEventListener('scroll', handleScroll, { capture: true });
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  // 스크롤하면 탭 자동선택 함수
  function handleScroll (){
    if (!detailInfoRef.current || !document.getElementById('app')) {
      return;
    }

    // 현재 스크롤 위치를 담은 변수
    const scrollTop: number | undefined = document.getElementById('app')?.scrollTop;
    
    if (scrollTop) {
      // 스크롤 하는 중일 경우 → 초기화 해버리자
      if (scrolling.current) { 
        if (!scrollTarget.current || scrollTop === scrollTarget.current) {
          scrollTarget.current = null;
          scrolling.current = false;
        }
        return;
      }

      // 상세정보 영역인 경우 → 메뉴ID를 '상세정보'로 변경
      if (scrollTop >= detailInfoRef.current.offsetTop - offset) {
        setMenuID(1);
        
      // 포트폴리오 영역인 경우 → 메뉴ID를 '포트폴리오'로 변경 
      } else if (scrollTop < detailInfoRef.current.offsetTop - offset) {
        setMenuID(0);
      }
    }
  }

  // 클릭시 이동
  function setMenu (index) {
    if (menu.length <= index) return; // 예외처리
    
    let top = 0;
    setMenutID(index); 	// (현재클릭된) 메뉴ID를 변경

    // '포트폴리오' 영역을 클릭했을 때 (스크롤 위치 설정)
    if (index === 0) {
      if (!portfolioRef.current) return;    
 	  top = portfolioRef.current.offsetTop - offset;
    }
    
    // '상세정보' 영역을 클릭했을 때 (스크롤 위치 설정)
    if (index === 1) {
      if (!detailInfoRef.current) return; 
      top = detailInfoRef.current.offsetTop - offset;
    }
    
    scrollTarget.current = top;
    scrolling.current = true;
    document.getElementById('app')?.scrollTo({ top: top, behavior: 'smooth' });
  }
  
  return (
    <div className="product-detail">
      <Tabs			// 메뉴탭 컴포넌트이다
        items={menu} 		// 메뉴 배열을 받아 보여주고
        activeID={menuID} 	// 클릭된 탭의 메뉴ID 를 내려보내주기도 하고
        setActiveID={setMenuID} // 클릭할 탭의 메뉴ID 를 여기서 조작하게 해주기도 한다.
      />

      <div className="product-content">
        <MarketPortfolio ref={portfolioRef} /> // 각각의 컴포넌트에 useRef 로 DOM 에 접근하자
        <MarketDetailInfo ref={detailInfoRef} />
      </div>
    </div>
  );
};

export default ProductDetail;

 

반응형

댓글