[🚨 Error 🚨] css animation μ—λŸ¬(slideIn / slideOut)

우혁·2024λ…„ 4μ›” 25일

🚨 Error 🚨

λͺ©λ‘ 보기
6/11

λ¬Έμ œκ°€ λ°œμƒν•œ λ°°κ²½

νŒ€μ›λΆ„μ΄ λ§Œλ“€μ–΄μ£Όμ‹  μ‚¬μ΄λ“œ 메뉴 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•΄μ„œ νŽ˜μ΄μ§€ μ‘°λ¦½ν•˜λŠ”λ° μƒˆλ‘œκ³ μΉ¨ μ‹œ μ‚¬μ΄λ“œ 메뉴가 λ‚˜μ™”λ‹€κ°€ λ“€μ–΄κ°€λŠ” 이슈λ₯Ό λ°œκ²¬ν•œ 상황, 기쑴에 λ§Œλ“€μ–΄μ£Όμ…¨μ„ λ•Œλ„ μ΄μŠˆκ°€ μžˆμ—ˆμ§€λ§Œ 배경색이 투λͺ…μœΌλ‘œ λ˜μ–΄μžˆμ–΄ 인지 ν•˜μ§€ λͺ»ν•œ 상황

  • 문제 상황

λ¬Έμ œκ°€ λ°œμƒν•œ 원인 μ•Œμ•„λ³΄κΈ°

μ²˜μŒμ—λŠ” μ‚¬μ΄λ“œ 메뉴가 isOpen μƒνƒœλ‘œ 쑰건뢀 λ Œλ”λ§μ„ μ€˜μ„œ mount, unmount ν•˜λŠ” 방식인 쀄 μ•Œμ•˜μ§€λ§Œ, css μ†μ„±μœΌλ‘œ transform: translateX(-100%) λ·°ν¬νŠΈμ—λ§Œ 보이지 μ•Šκ²Œ ν•˜λŠ” λ°©μ‹μ΄μ˜€λ‹€.

  • μ»΄ν¬λ„ŒνŠΈ μ½”λ“œ
function SideMenu({ dashboards }: SideMenuProps) {
  const [isOpen, setIsOpen] = useAtom(sideMenuAtom);
  const [renderDelayed, setRenderDelayed] = useState(false);
  const sideMenuRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleSideMenu = (e: MouseEvent) => {
      const target = e.target as HTMLElement;
      if (target.dataset.state === 'sideMenuToggle') {
        setIsOpen((prev) => !prev);
        return;
      }

      if (sideMenuRef.current && !sideMenuRef.current.contains(target)) {
        setIsOpen(false);
        return;
      }
    };

    document.addEventListener('click', handleSideMenu);


    const timeout = setTimeout(() => {
      setRenderDelayed(true);
    }, 400);

    return () => {
      document.removeEventListener('click', handleSideMenu);
      clearTimeout(timeout);
    };
  }, [setIsOpen]);

	// λ‚˜λ¨Έμ§€ μ»΄ν¬λ„ŒνŠΈ μ½”λ“œ...
}
  • CSS μ½”λ“œ
@keyframes slideIn {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes slideOut {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

.sideMenu {
  width: 18.75rem;
  height: calc(100vh - 4.3rem);
  background-color: $color-white-ffffff;
  position: fixed;
  top: 4.3rem;
  left: 0;
  animation: slideOut 0.4s ease forwards;
  z-index: 999;

  @include mobile {
    width: 16rem;
    top: 3.75rem;
    height: calc(100vh - 3.75rem);
  }
}

.open {
  animation: slideIn 0.4s ease forwards;
}

μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ 초기 클래슀 λ„€μž„μ— animation: slideOut 0.4s ease forwards이 μ„€μ •λ˜μ–΄ μžˆμ–΄ μƒˆλ‘œκ³ μΉ¨ν•˜λ©΄ 첫 λ Œλ”λ§ μ‹œμ— transform: translateX(0)μ΄μ˜€λ‹€κ°€ transform: translateX(-100%) 으둜 λ³€ν•˜κΈ° λ•Œλ¬Έμ— μ‚¬μ΄λ“œ 메뉴가 λ³΄μ˜€λ‹€κ°€ μ‚¬λΌμ§€λŠ” ν˜„μƒμ΄ 생기고 μžˆλ‹€!


문제 ν•΄κ²° 방법

첫 λ Œλ”λ§μ‹œμ— slideOut μ• λ‹ˆλ©”μ΄μ…˜ μž‘λ™ν•΄μ„œ λ·°ν¬νŠΈμ— λ³΄μ˜€λ‹€κ°€ μ‚¬λΌμ§€λŠ” 것이 문제이기 λ•Œλ¬Έμ— κΈ°λ³Έ μŠ€νƒ€μΌμ— transform: translateX(-100%)λ₯Ό μ§€μ •ν•˜κ³  첫 λ Œλ”λ§μ΄ μ™„λ£Œλ˜μ—ˆλ‹€λŠ”λΌλŠ” 것을 μ•Œ 수 μžˆλŠ” stateλ₯Ό ν•˜λ‚˜ 생성해 첫 λ Œλ”λ§μ΄ μ™„λ£Œλ˜λ©΄ κ·Έ 이후에 slideOut μ• λ‹ˆλ©”μ΄μ…˜μ„ μ§€μ •ν•˜μ—¬ λ‚΄κ°€ μ›ν•˜λŠ” λ™μž‘μ„ μ‹œμΌœμ€€λ‹€.

  • μˆ˜μ • ν›„ μ»΄ν¬λ„ŒνŠΈ μ½”λ“œ
function SideMenu({ dashboards }: SideMenuProps) {
  const [isOpen, setIsOpen] = useAtom(sideMenuAtom);
  const [isFirstRender, setIsFirstRender] = useState(false);
  
   useEffect(() => {
    const handleSideMenu = (e: MouseEvent) => {
      const target = e.target as HTMLElement;
      if (target.dataset.state === 'sideMenuToggle') {
        setIsOpen((prev) => !prev);
        return;
      }

      if (sideMenuRef.current && !sideMenuRef.current.contains(target)) {
        setIsOpen(false);
        return;
      }
    };

    // 첫 λ Œλ”λ§ μ‹œμ—λŠ” slideOut μ• λ‹ˆλ©”μ΄μ…˜ μž‘λ™ x
    setIsFirstRender(true);

     // 첫 λ Œλ”λ§μ΄ λλ‚˜κ³  isOpen이 false인 κ²½μš°μ— slideOut μ• λ‹ˆλ©”μ΄μ…˜μ„ λΆ€μ—¬ν•œλ‹€.
    if (isFirstRender && !isOpen) {
      sideMenuRef.current?.classList.add(styles.close);
    }

    document.addEventListener('click', handleSideMenu);

    const timeout = setTimeout(() => {
      setRenderDelayed(true);
    }, 400);

    return () => {
      document.removeEventListener('click', handleSideMenu);
      clearTimeout(timeout);
    };
  }, [isOpen]);

  // λ‚˜λ¨Έμ§€ μ»΄ν¬λ„ŒνŠΈ μ½”λ“œ../
}
  • μˆ˜μ • ν›„ CSS μ½”λ“œ
@keyframes slideIn {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes slideOut {
  from {
    transform: translateX(0%);
  }
  to {
    transform: translateX(-100%);
  }
}

.sideMenu {
  width: 18.75rem;
  height: calc(100vh - 4.3rem);
  background-color: $color-white-ffffff;
  position: absolute;
  top: 4.3rem;
  left: 0;
  transform: translateX(-100%); // 기본적으둜 λ·°ν¬νŠΈμ— μ•ˆλ³΄μ΄κ²Œ μ„€μ •
  z-index: 999;

  @include mobile {
    width: 16rem;
    top: 3.75rem;
    height: calc(100vh - 3.75rem);
  }
}

.close { // 첫 λ Œλ”λ§μ΄ 되고, isOpen이 false인 κ²½μš°μ— μ• λ‹ˆλ©”μ΄μ…˜ λΆ€μ—¬
  animation: slideOut 0.4s ease forwards;
}

.open {
  animation: slideIn 0.4s ease forwards;
}

μ΅œμ’… λ™μž‘ μ˜μƒ

이제 μƒˆλ‘œκ³ μΉ¨ ν•˜λ”λΌλ„ μ²˜μŒμ—λŠ” μ• λ‹ˆλ©”μ΄μ…˜μ΄ μ—†λŠ” μƒνƒœλ‘œ μ‚¬μ΄λ“œ 메뉴가 λ·°ν¬νŠΈμ— 보이지 μ•Šμ•„ μ΄μŠˆκ°€ ν•΄κ²°λ˜μ—ˆλ‹€!

profile
🏁

0개의 λŒ“κΈ€