||Project1|| #11 사이드메뉴 열고 닫기 (w/ 애니메이션)

윤코코·2021년 12월 22일
2
post-thumbnail

드디어 마무리!!!

왼쪽 상단의 "메뉴" 버튼을 누르면 사이드 메뉴가 나타났다가
"X"를 누르면 사라지도록 하는 것이다.
이건 앞서 배워왔던게 있으니 금방 할 줄 알았다. 근데 아니었다.ㅠㅠ

transition을 많이 써보지 않아서 이 부분이 어려웠다.
메뉴를 띄우고 없애는건 금방 구현이 되었다.

🚪 메뉴를 열고 닫는 3가지 방법

1. useState

모달을 열고 닫던 방식과 동일하게 처음에는 이 방법을 사용했다.

(생략)
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)

return 
<>
  {isMenuOpen ? <Menu/> : null} 
  <button onClick={setIsMenuOpen(true)}>메뉴</button>
</>
(생략)

그런데 이 방법으로 하자니 소ㅑㅇ 하고 열리고 소ㅑㅇ 하고 닫히는 애니메이션을 넣을 수 없었다.

2. display

그래서 css로 열고닫기를 컨트롤 하고 transition을 주기로 해보았다.
결론은 이것도 열고닫기는 되지만 애니메이션은 실패.

const SideMenu = () => {
  const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
  const sideMenuContainerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!sideMenuContainerRef || !sideMenuContainerRef.current) return;

    sideMenuContainerRef.current.style.display = isMenuOpen
      ? "inline-block"
      : "none";
    sideMenuContainerRef.current.style.transition = "all 0.5s ease-in-out";
  }, [isMenuOpen]);

  return (
    <>
      <HeaderBtn onClick={() => setIsMenuOpen(!isMenuOpen)}>메뉴</HeaderBtn>
      <SideMenuContainer ref={sideMenuContainerRef}>
        <MenuTitleClose onClick={() => setIsMenuOpen(!isMenuOpen)}>
          X
        </MenuTitleClose>
      </SideMenuContainer>
    </>
  );
};

왜 안되나 찾아보았다.
transition은 이전값에서 변경되는 값을 비교해서 작용하는데
display: none은 아예 렌더링 트리에 그려지지 않기 때문에 비교할 초기값이 없어 안된다고 한다.

MDN의 display 페이지 중...
Using a display value of none on an element will remove it from the accessibility tree. This will cause the element and all its descendant elements to no longer be announced by screen reading technology.

3. visibility

이번에는 visibility를 사용해보기로 했다.
스크린리더에는 인식이 되지 않아야 하니까 너비같은 다른 속성보다는 아예 안보이게는 해야한다고 생각했기 때문이다.

visibility와 display의 차이
visibility: hidden은 display: none과 다릅니다. 전자는 요소를 보이지 않게 만들지만, 이 요소는 여전히 레이아웃에서 공간을 차지합니다(즉, 비어 있는 상자로 렌더링됨). 반면, 후자(display: none)는 요소가 보이지 않으며 레이아웃에 포함되지도 않도록 렌더링 트리에서 요소를 완전히 제거합니다.
(* 출처: Ilya Grigorik님의 [렌더링 트리 생성, 레이아웃 및 페인트])

하... 안된다.
visibility는 수치값이 아닌 상태값이 변하는 것이기 때문에
계산에 의해 움직이는 transition이 적용될 수 없다고 한다.

(* 출처: stackoverflow의 [CSS transition with visibility not working])

⏯ 메뉴 애니메이션

메뉴를 껐다켰다 하는 속성으로는 애니메이션을 넣을 수 없으니
가장 상단에서 전체를 감싸는 wrapper에 visibility를 visible | none으로 설정해두고 그 안에 들어있는 메뉴의 배경과 콘텐츠 부분에 애니메이션을 주기로 했다.

[1st try] width

width를 원래 지정해두었던 너비에서 0까지 왔다갔다 하게 하고
거기에 transition을 주었다.

뭐 되긴 된다.
하지만 width가 줄어드는 동시에 안에 들어있는 텍스트들도 어그러지면서 없어지는게 눈에 보였다.

[2nd try] opacity

opacity를 0에서 1 사이를 왔다갔다 하게 했다.
엄.. 근데 희미하게 사라지는 시점에
바닥에 있는 요소와 메뉴 요소의 잔상이 겹쳐 보여서 깔끔하지 않았다.

[3rd try] translateX

결론은 translateX였다.
chading님의 [CSS Transition 활용 TIP (부드러운 움직임)]
여기서 답을 찾았다.
왜 이걸 생각을 못했을까.
translateX를 0에서 100% 사이를 왔다갔다 하게 하면
처음에 내가 원했던 옆으로 소ㅑㅇ 사라지고 나타나게 할 수 있다!

🎊 완성된 사이드메뉴 코드

그럼 이렇게 사이드 메뉴까지 완성!
👉 SideMenu.tsx 전체 코드 보기

🛠 보완할 점

# 확장성

메뉴의 각 item도 list화 시켜서 map으로 순회해 그리는 방식으로 하면 확장성이 높아질 거라고 어디선가 보았다. 메뉴가 추가되면 리스트에 텍스트만 넣어주면 되니까. 추후에 보완할때는 그렇게 해봐야겠다.

# 반응형

화면이 충분히 넓어지면 사이드메뉴가 왼쪽에 고정적으로 보이도록 만들어주면 좋겠다.

# Styled-Components로 다 해결하기

Styled-Components에 props에 따라 css를 바꿔줘보려고 했지만
transition이 그 변화를 감지하지 못하는 듯 했다.
그래서 찾아보니 keyframe이라는 걸 사용해야 한다는데
다음에는 이것도 써보는걸로!
Agal님의 [13. Styled-components : 트랜지션 구현하기 - 컴포넌트 스타일링 | 벨로퍼트]
Seungho Lee님의 [리액트 styled components 애니메이션 구현]

- 끝!!!!!!!!!!!!!! -

|| 참고 ||

leejiwon6315님의 [반응형 네비게이션 만들기]
어포스트님의 [CSS로 반응형 사이드바 메뉴 만들기]
9rganizedchaos님의 [2021/1/8 👨🏻‍💻 반응형 웹 만들기 따라하기 도전]
MDN의 [display]
dev-tinkerbell님의 [display none이 transition이 안먹히는 이유]
친절한 송송 DEV님의 [css display none 애니메이션 오류 수정하기]
chading님의 [CSS Transition 활용 TIP (부드러운 움직임)]
털업님의 [CSS : cubic-bezier란?]

profile
Web Front-End Developer

0개의 댓글