Accordion 컴포넌트 만들기 - 애니메이션

jh·2024년 7월 22일

디자인 시스템

목록 보기
7/14

아코디언이 열리고 접힐 때의 애니메이션을 적용해줘야 하는데
height 값에 따른 애니메이션 적용해야 할 때, height의 정확한 값이 존재해야 한다

참고자료
스택오버플로우

보통은 max-height 를 사용하거나, 임의의 height 값을 적용하는 방식으로 해결하는 것 같다

하지만 실제 height가 아니라 임의의 값을 사용한다는 점이 좀 걸렸다

height 속성에 정확한 값을 넣어줄 수 없는 이유는

  • css 속성을 정의할 당시에는 height 가 얼마인지 알 수 없다

그렇다면 아코디언이 열려 있을 때, 해당 height를 구해서 css에 적용시켜주면 된다

useAccordionHeight

import { useEffect, useRef } from "react"

export const useAccordionHeight = <T extends HTMLElement>(
  isOpen: boolean,
  duration
) => {
  const contentRef = useRef<T>(null)
  useEffect(() => {
    const element = contentRef.current
    if (element === null || !element.parentElement) {
      return
    }
    const { parentElement } = element
    if (isOpen) {
      parentElement.style.display = "block"

      const height = element.style.getPropertyValue("--accordion-height")

      if (height === "0" || !height) {
        element.parentElement!.style.setProperty(
          "--accordion-height",
          `${element.clientHeight}px`,
        )
      }
    } else {
      setTimeout(() => {
        parentElement.style.display = "none"
        parentElement.style.setProperty(
          "--accordion-height",
          `0`,
        )
      }, duration)
    }
  }, [isOpen])

  return contentRef
}
        
@keyframes accordionDown {
  0% {
    height: 0;
  }
  to {
    height: var(--accordion-height);
  }
}
@keyframes accordionUp {
  0% {
    height: var(--accordion-height);
  }
  to {
    height: 0;
  }
}

간단하게 말하면 height를 css 변수를 사용해서, 열리고 닫힐 때 해당 변수값을 변경하는데

isOpen이 true 이면 -> 해당 element의 display를 block으로 변경 후 clientHeight를 조사해서 --accordion-height 를 clientHeight로 변경한다

isOpen이 false면, 타이머를 통해 몇초 뒤에 display를 none으로 변경 후 --accordion-height 를 0으로 변경한다

export const AccordionContent = ({
  children,
}: PropsWithChildren<{ duration?: number }>) => {
  const { isOpen, value } = useAccordionItemProvider("accordionItem")

  const contentRef = useAccordionHeight<HTMLDivElement>(isOpen, 150)

  return (
    <div
      className={css({
        display: "none",
        overflow: "hidden",
        border: "1px solid black",
        animation: isOpen
          ? `accordionDown 0.2s cubic-bezier(.4,0,.2,1)`
          : `accordionUp 0.2s cubic-bezier(.4,0,.2,1)`,
      })}
      data-state={isOpen ? "open" : "close"}
      aria-controls={value}
    >
      <div ref={contentRef}>{children}</div>
    </div>
  )
}

타이머는 실제 애니메이션 동작 시간보다 작게 설정해야 한다.
애니메이션 0.2s - 타이머 0.15s 정도로 설정했는데 자연스러운 것 같다

0개의 댓글