CSS/공용 ui

[css, 공용 ui, js] max-height, max-width로 접었다 피는 폴딩 ui 반응형 만들기(% 단위를 사용하지 못할 때)

tea-tea 2024. 9. 21. 21:27

클릭 시, 접었다 펴지는 ui

 

목적

폴딩 ui는 css의 transtion과 max-height, max-width 속성을 이용해서 아주 간단하게 만들 수 있다.

문제는 화면의 크기가 가변적인 상황에서 반응형 ui로 만들때인데,

ui가 펴지는 상황에서 max-height와 max-width에 퍼센티지(%)를 사용할 수 없는 경우가 있다.

이럴 때에는 펴지는 상황의 예상크기를  js로 계산하여 직접 스타일을 주어야 하는데, 그럴 때 사용하는 ui다.

 

원리

이 폴딩 ui는 최초에 접힌 상태로, max-width와 max-height가 0으로 설정되어 있다.

우선, 최초에 document가 로드된 시점에 max-width, max-height 제한을 풀어서 ui의 원래 크기로 되돌린다.

그리고 이 크기를 변수로 저장한 후 다시 접힌 상태로 스타일을 되돌린다. (이 과정은 아주 빨라서 제한을 푼 펴진 상태가 되기 전에 끝난다. 즉, 리플로우가 일어나지 않고 깜박임 현상도 없다.)

 

이제 유저가 ui를 펴는 이벤트를 발생시키면 저장했던 예상 크기의 변수로 스타일을 설정해주면 된다.

 

소스코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .app {
        border: 1px solid black;
        overflow: hidden;
        transition: all 0.25s ease-in-out;
      }

      .close {
        max-width: 0;
        max-height: 0;
      }
    </style>
  </head>
  <body>
    <button class="toggle">toggle</button>
    <div class="app close">
      <div>
        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Asperiores
        explicabo numquam at error, incidunt, sunt doloremque enim facere
        similique atque odit! Laudantium, voluptatum minus a dicta officiis
        cumque facere rerum.
        <br />
        Lorem ipsum dolor, sit amet consectetur adipisicing elit. Asperiores
        explicabo numquam at error, incidunt, sunt doloremque enim facere
        similique atque odit! Laudantium, voluptatum minus a dicta officiis
        cumque facere rerum.
      </div>
    </div>

    <script>
      (function () {
        const dom = {
          toggle: document.querySelector(".toggle"),
          app: document.querySelector(".app"),
        };

        class expectDomStyle {
          dom = {
            target: undefined,
            style: {
              fold: {
                maxWidth: undefined,
                maxHeight: undefined,
              },
              unfold: {
                maxWidth: undefined,
                maxHeight: undefined,
              },
            },
          };

          constructor(dom) {
            this.dom.target = dom;
            this.calcExpectStyle();
          }

          /// dom을 fold했을 때 예상 크기와 unfold했을 때 예상 크기를 상태변수로 저장
          calcExpectStyle() {
            this.dom.style.fold.maxWidth = this.dom.target.style.maxWidth || "";
            this.dom.style.fold.maxHeight =
              this.dom.target.style.maxHeight || "";
            this.dom.target.style.maxWidth = "none";
            this.dom.target.style.maxHeight = "none";
            this.dom.style.unfold.maxWidth = this.dom.target.scrollWidth;
            this.dom.style.unfold.maxHeight = this.dom.target.scrollHeight;

            this.dom.target.style.maxWidth = "";
            this.dom.target.style.maxHeight = "";
          }

          // dom에 unfold 시 예상크기를 style 세팅
          setExpectStyle() {
            this.dom.target.style.maxWidth =
              this.dom.style.unfold.maxWidth + "px";
            this.dom.target.style.maxHeight =
              this.dom.style.unfold.maxHeight + "px";
          }

          // dom을 fold
          // style 속성 변경
          clearExpentStyle() {
            this.dom.target.style.maxWidth = this.dom.style.fold.maxWidth;
            this.dom.target.style.maxHeight = this.dom.style.fold.maxHeight;
          }
        }

        const appExpectDomStyle = new expectDomStyle(dom.app);

        dom.toggle.addEventListener("click", () => {
          if (dom.app.classList.contains("open")) {
            appExpectDomStyle.clearExpentStyle();
          } else {
            appExpectDomStyle.setExpectStyle();
          }
          dom.app.classList.toggle("open");
        });
      })();
    </script>
  </body>
</html>