사이드바 애니메이션 만들기 + 이슈 해결

J·2023년 10월 9일
0

al-ways

목록 보기
7/8
post-thumbnail

라이브러리를 사용하지 않고 슬라이드 애니메이션이 들어간 사이드바를 만들어봤다.

초기 코드

// Router.tsx

...

const Router = () => {
  const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
  const toggleMenu = () => {
    setIsMenuOpen(!isMenuOpen);
  };

  return (
    <BrowserRouter>
      <HeaderMolecule toggleMenu={toggleMenu} />
      {isMenuOpen && (
        <SidebarOrganism toggleMenu={toggleMenu} isMenuOpen={isMenuOpen} />
      )}
      {url == '/' ? null : <PrevMolecule />}
      <Routes>
        <Route path="/" element={<Main />} />
        				...
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
// SidebarOrganism.tsx

...

interface SidebarProps {
  toggleMenu?: () => void;
  isMenuOpen: boolean;
}

const SidebarOrganism = ({ toggleMenu, isMenuOpen }: SidebarProps) => {
	...
  return (
    <SidebarWrapper onClick={toggleMenu}>
      <Sidebar isMenuOpen={isMenuOpen} onClick={handleSidebarClick}>
        <Close toggleMenu={toggleMenu} />
        <SidebarUserStateMolecule />
        <BreakLine width={'280'} height={'5'} mb={'30'} />
        {menuItems.map((item, index) => (
          <SidebarMenuMolecule
            key={index}
            txt={item.txt}
            onClick={() => {
              navigate(item.route);
              if (toggleMenu) toggleMenu();
            }}
          />
        ))}
        <Text
          width={'280'}
          height={'16'}
          fonts={'16'}
          fontw={'700'}
          bottom={'20'}
          cursor={'pointer'}
          color={COLORS.gray}
          position={'absolute'}
          txt={'로그아웃'}
        />
      </Sidebar>
    </SidebarWrapper>
  );
};

export default SidebarOrganism;


이슈

열릴 때의 애니메이션은 정상 작동하지만 반대의 닫히는 애니메이션은 동작하지 않음.

당장은 이유를 잘 모르겠어서
1. 연관된 4개의 컴포넌트를 번갈아 고치기 싫어 관련 코드 구조를 변경하고
2. 추가로 animation의 상태를 만들어 따로 관리를 해보기로 했다.


변경한 코드

// Router.tsx

...

const Router = () => {

  const [isSidebarOpen, setisSidebarOpen] = useState<boolean>(false); // 상태명 변경함

  return (
    <BrowserRouter>
      <HeaderMolecule setisSidebarOpen={setisSidebarOpen} />
      {isSidebarOpen && <SidebarOrganism setisSidebarOpen={setisSidebarOpen} />}
      {url == '/' ? null : <PrevMolecule />}
      <Routes>
        <Route path="/" element={<Main />} />
						...
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
// SidebarOrganism.tsx

...

interface AnimationProps {
  animation: typeof slideIn | typeof slideOut;
}

const SidebarOrganism = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();

  // close 시 애니메이션 동작하지 않아 상태 추가
  const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);

  // SidebarWrapper or Close 클릭 시 side bar 닫기
  const handletoggleSidebar = () => {
    setSidebarOpen(!sidebarOpen);

    // side bar를 닫을 때 setTimeOut 시간 후 sidebarWrapper를 없애주기
    if (!sidebarOpen) {
      setTimeout(() => {
        dispatch(toggleSidebar());
      }, 200); // 애니메이션의 지속 시간과 같게 설정하면 깜빡임 발생함
    }
  };

  // 사이드바 내 요소가 아닌 부분 클릭 시 상태 유지 (현재 사이드바를 벗어나지 않도록 함)
  const handleSidebarClick = (e: React.MouseEvent) => {
    e.stopPropagation();
  };

  // 사이드바 메뉴
  const menuItems = [
    { txt: '검색', route: '/search' },
    { txt: '지도', route: '/map' },
    { txt: '전체 리스트', route: '/list' },
    { txt: '마이페이지', route: '/my' },
  ];

  return (
    <SidebarWrapper onClick={handletoggleSidebar}>
      <Sidebar
        onClick={handleSidebarClick}
        animation={sidebarOpen ? slideOut : slideIn}
      >
        <Close handletoggleSidebar={handletoggleSidebar} />
        <SidebarUserStateMolecule />
        <BreakLine width={'280'} height={'5'} mb={'30'} />
        {menuItems.map((item, index) => (
          <SidebarMenuMolecule
            key={index}
            txt={item.txt}
            onClick={() => {
              navigate(item.route);
              // 라우터 이동 시 사이드바 닫히게
              if (handletoggleSidebar) handletoggleSidebar();
            }}
          />
        ))}
        <Text
          width={'280'}
          height={'16'}
          fonts={'16'}
          fontw={'700'}
          bottom={'20'}
          cursor={'pointer'}
          color={COLORS.gray}
          position={'absolute'}
          txt={'로그아웃'}
        />
      </Sidebar>
    </SidebarWrapper>
  );
};

export default SidebarOrganism;

먼저,
1. 기존 라우터에서 관리하던 사이드바 여닫힘 전환을 헤더로 옮겼다.
2. 그리고 라우터에서는 사이드바 상태만 관리하고 set 함수를 헤더와 사이드바 컴포넌트에 데이터를 넘겨줬다.
3. props로 받은 set 함수들은 각 컴포넌트에서 사이드바 상태만 관리한다.
4. ( + 그 다음 사이드바 컴포넌트에서 애니메이션 상태를 따로 추가했다.)
5. RTK 리팩토링 및 변수명들도 수정함.

  const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);


  // SidebarWrapper or Close 클릭 시 side bar 닫기
  const handletoggleSidebar = () => {
    setSidebarOpen(!sidebarOpen);

    // side bar를 닫을 때 setTimeOut 시간 후 sidebarWrapper를 없애주기
    if (!sidebarOpen) {
      setTimeout(() => {
        dispatch(toggleSidebar());
      }, 200); // 애니메이션의 지속 시간과 같게 설정하면 깜빡임 발생함
    }
  };
const Sidebar = styled.div<AnimationProps>`
  width: 320px;
  height: 909px;

  align-items: center;
  display: flex;
  flex-direction: column;
  position: absolute;
  background-color: #141414;

  animation: ${(props) => props.animation} 0.25s ease-out;
`;

해결

사이드바 컴포넌트에서 따로 애니메이션을 관리하니 정상 동작함.

profile
벨로그로 이사 중

0개의 댓글